Migration Guide 4.X to 5.X¶
This version utilizes Discord API v10, and includes several breaking changes and improvements.
Dependency Installation¶
Before continuing, we should mention that JDA versions are now distributed via Maven Central. You can remove the old m2.dv8tion.net resolver from your build files.
Replace VERSION with the latest version
repositories {
mavenCentral()
}
dependencies {
implementation("net.dv8tion:JDA:VERSION")
}
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>VERSION</version>
</dependency>
Additional Resources¶
This migration guide does not include every single detail and does not focus on any new features available in JDA 5, such as ThreadChannels. Here are some useful resources to learn more if you are curious about all the new things we added:
Extensions That Support 5.X¶
Here is a list of known extensions that support 5.X. The ones that are not checked do not support it yet. You should check that everything used in your project supports 5.X before starting migration.
- jda-reactor
- LavaPlayer
- jda-nas
- udpqueue.rs (for minimal Rust bindings)
- jda-ktx
- JDA-Utilities
Introduction of Message Content Privileged Intent¶
As part of upgrading to API v10, accessing the following user-generated content in messages now requires the Message Content privileged intent (GatewayIntent.MESSAGE_CONTENT):
- Text content (
Message#getContentRaw,Message#getContentDisplay,Message#getContentStripped) - Embeds (
Message#getEmbeds) - Attachments (
Message#getAttachments) - Message Components (
Message#getActionRows,Message#getButtons) - Custom Emoji Mentions (
Message#getMentions()#getCustomEmojis())
You must enable this intent in your JDABuilder AND in the Discord Developer Portal for your app if you utilize any of the above methods. For more information on intents, read the dedicated wiki page.
Channel Rework¶
There are several breaking changes to GuildChannel and ChannelManager. Firstly, the packages of most channel interfaces have changed. What was previously simply found in net.dv8tion.jda.api.entities is now split over the following packages:
net.dv8tion.jda.api.entities.channel
Top level channel enums and the newChanneltype interface.net.dv8tion.jda.api.entities.channel.attribute
Interfaces dedicated to specific fields, such as slowmode or permissions. This also has some interfaces for concepts, such as copying or categorizing.net.dv8tion.jda.api.entities.channel.concrete
The bottom level interfaces for exact concrete types, such asVoiceChannelandTextChannel.net.dv8tion.jda.api.entities.channel.forums
Types related toForumChannel, such as tags.net.dv8tion.jda.api.entities.channel.middleman
Abstractions that apply to multiple concrete types, such asMessageChannelwhich covers all message related methods.net.dv8tion.jda.api.entities.channel.unions
Interfaces used for return values, allowing for downcasting into specific types. They also provided shared features of the specific interface they extend.
Channel Return Types¶
Many classes in JDA had specific getters for each channel type. For example, messages had getTextChannel() and getPrivateChannel(). The goal was to allow easy conversion to concrete types that have more functionality than their abstract counterpart (such as MessageChannel). In JDA v5, we've introduced new Channel Union interfaces to deal with this problem in a more elegant way. Instead of using foo.getTextChannel(), you now use foo.getChannel().asTextChannel(). The getChannel() method now often returns a specific Union type such as MessageChannelUnion, which doubles as the abstract implementation for MessageChannel (allows to send messages) and also downcasting via various asX() methods.
New Channel Attribute Interfaces¶
GuildChannel now implements getGuild and getManager only. It's meant to serve as a generic type to hold channels from guilds.
Specific channel attributes, such as slowmode and permissions, have been split into several interfaces. These interfaces can be found in the net.dv8tion.jda.api.entities.channel.attribute package. Each interface in this package extends GuildChannel.
Permission Access Changes¶
With JDA v5 we reduced the capabilities of the GuildChannel interface. The method GuildChannel#getPermissionContainer has been introduced to make accessing permissions easier. This allows for users to confidently access the entity that dictates the permissions for a given channel without having to check whether the channel actually supports permissions, such as in the case of threads.
Of course, if you have a channel type that already supports IPermissionContainer, you don't need this getter.
Type-Trimmed Channel Managers¶
In JDA v4, GuildChannel#getManager returned a ChannelManager that gave every possible setter for every channel type. This caused for UnsupportedOperationException or IllegalStateException to be thrown in some cases, such as calling setBitrate on a TextChannel.
JDA v5 provides type-trimmed channel managers, which provide only the setters that we know for sure can work on the given channel. Each of these managers can be found in the net.dv8tion.jda.api.managers.channel package. These all follow the same implement/extension hierarchy as the channels do and map 1:1.
Independent Stage Channel and News Channel Entities¶
StageChannel and NewsChannel were previously variants of VoiceChannel and TextChannel. These are now their own independent entities. This comes with a number of changes:
ChannelAction#setNewsis replaced byChannelAction.setTypeChannelManager#getTypeis replaced byChannelManager#getChannel()#getTypeChannelManager#setNewsis replaced byChannelManager#setTypeTextChannel#crosspostMessageis replaced byNewsChannel#crosspostMessageTextChannel#followis replaced byNewsChannel#followTextChannel#isNewswas removed
Other Changes¶
StoreChannelwas removedPrivateChannel#getUseris now nullableMessageChannel#getLatestMessageIdandMessageChannel#getLatestMessageIdLongno longer change to null if the message was deleted
Permission Changes¶
A number of permissions have been renamed, with one permission being removed entirely.
Permission.MANAGE_EMOTESrenamed toPermission.MANAGE_EMOTES_AND_STICKERSPermission.MESSAGE_WRITErenamed toPermission.MESSAGE_SENDPermission.USE_SLASH_COMMANDSrenamed toPermission.USE_APPLICATION_COMMANDSPermission.USE_PUBLIC_THREADSrenamed toPermission.CREATE_PUBLIC_THREADSPermission.USE_PRIVATE_THREADSrenamed toPermission.CREATE_PRIVATE_THREADSPermission.MESSAGE_READwas removed in favor ofPermission.VIEW_CHANNEL
Event Changes¶
There are several changes related to events.
Removal of Guild Context Message Events¶
All message event types for guild messages have been removed. Examples of these are:
Generic<Context>MessageEvent(ex:GenericGuildMessageEvent)<Context>MessageReceivedEvent(ex:GuildMessageReceivedEvent)<Context>MessageUpdateEvent(ex:GuildMessageUpdateEvent)<Context>MessageDeleteEvent(ex:GuildMessageDeleteEvent)<Context>MessageEmbedEvent(ex:GuildMessageEmbedEvent)<Context>MessageReaction<X>Event(ex:GuildMessageReactionAddEvent)
Instead, you should use the unified versions of these events:
GenericMessageEventMessageReceivedEventMessageUpdatedEventMessageDeletedEventMessageReaction<X>Event(ex:MessageReactionAddEvent)
You can check if a message is from a Guild by using Message#isFromGuild.
Changes to Specifying GatewayIntents from Event Types¶
With context-specific events being removed (such as GuildMessageReceivedEvent), you can no longer use these events to specify GatewayIntent.GUILD_MESSAGES or GatewayIntent.DIRECT_MESSAGE_REACTIONS. You may supply any of the unified message events, but the functionality will be slightly different. It is not recommended to use GatewayIntent#fromEvents if you wish to have finer grain control around intents for messages.
Session Events¶
All events that update the gateway session of a bot now extend a common GenericSessionEvent. Such events are located within the net.dv8tion.jda.api.events.session package. This also includes ReadyEvent and ShutdownEvent.
Some events relating to sessions have been renamed:
DisconnectEventrenamed toSessionDisconnectEventReconnectedEventrenamed toSessionRecreateEventResumedEventrenamed toSessionResumeEvent
Voice State Events¶
GuildVoiceJoinEvent and GuildVoiceLeaveEvent have both been removed in favor of the unified GuildVoiceUpdateEvent.
As an example, to detect when a user leaves a voice channel, you can use GuildVoiceUpdateEvent#getChannelLeft. This method will return the channel that the user left, or null if the user joined a channel instead.
Intent and Member Cache Changes¶
A few changes to intents and member caching were also made to improve the user experience.
Renamed Intents¶
The following intents have been renamed to align with the API name convention:
GatewayIntent.GUILD_BANSrenamed toGatewayIntent.GUILD_MODERATIONGatewayIntent.GUILD_EMOJISrenamed toGatewayIntent.GUILD_EMOJIS_AND_STICKERS
Chunking and Caching¶
If you used ChunkingFilter.ALL in the past, JDA would automatically also cache all those members it chunked. However, this is not a very flexible rule, and we changed this behavior. Instead, the MemberCachePolicy will control which members to keep cached after chunking.
Sticker and Emoji Rework¶
We now fully support the sticker API in JDA. You can send up to 3 stickers in messages and receive both guild and nitro-only stickers. To improve the user experience with emotes/emoji we also changed this API.
Sticker Changes¶
The old MessageSticker class has been removed in favor of an assortment of new interfaces located in net.dv8tion.api.entities.sticker. Instead, Message#getStickers now returns the new StickerItem interface. You can create a sendable sticker instance with Sticker.fromId(stickerId) and then pass it to GuildMessageChannel#sendStickers.
Emote/Emoji Changes¶
In old versions we always made a distinction between Emote and Emoji to differentiate Custom Emoji from Unicode Emoji. This naming scheme was not ideal and caused a bit of confusion. In our redesign we instead simplify this to the names CustomEmoji and UnicodeEmoji. Some classes/interfaces had to be renamed for this new design:
Emoterenamed toRichCustomEmojiEmoteManagerrenamed toCustomEmojiManagerMessageReactionRemoveEmoteEventrenamed toMessageReactionRemoveEmojiEvent
All methods/enums/types using emote have also been adjusted to say emoji, for example Guild#retrieveEmotes is now Guild#retrieveEmojis and CacheFlag.EMOTE is now CacheFlag.EMOJI.
This new design also had the goal to unify all usages of emoji in the API, allowing you to use them for anything that makes use of emoji. For instance, previously there was a distinction between reaction emoji (MessageReaction.ReactionEmote) and button emoji (Emoji), which now both use the same Emoji type:
public void onMessageReactionAdd(MessageReactionAddEvent event) {
event.getChannel().sendMessage("User reacted")
.setActionRow(Button.primary("buttonid", event.getEmoji()))
.queue();
}
To check which emoji was used in a reaction, you can use emoji.equals(otherEmoji) instead of checking for id/name. This has the advantage for also checking the correct type for you. For example: event.getEmoji().equals(Emoji.fromFormatted("😃")).
You also now use Emoji instances for reactions. What was previously message.addReaction("...") is now message.addReaction(Emoji.fromFormatted("...")). And in general you can use the following factory methods to create emoji instances:
Emoji.fromUnicode
Parses codepoint notation like"U+1F602"or simply uses the provided unicode characters"😃". This returns a concreteUnicodeEmojitype instance.Emoji.fromCustom
Creates aCustomEmojiinstance from the provided name and id.Emoji.fromFormatted
Parses emoji instances from markdown such as"<:minn:12345581261712671>"and also supports unicode such as"😃"or codepoint notation"U+1F602". This returns aEmojiUnioninstance, which can be either custom or unicode.
You can see the full list of breaking changes in #2117.
Message Send/Edit Rework¶
In JDA 5 we are separating the handling of message sending and editing, while also unifying all send and edit functionality in the API. The old MessageBuilder and MessageAction have been split up:
MessageCreateBuilderMessageCreateActionMessageEditBuilderMessageEditAction
This change should only affect people who made use of MessageBuilder in the past. You need to update your code to either the edit or create builders. If you don't know whether the resulting operation is an edit or send request, you can simply always use MessageEditBuilder and then use MessageCreateData.fromEdit(MessageEditData) to convert it at call-site. You can also use similar factory methods to create a sendable message from the Message interface (MessageCreateData.fromMessage(message)).
Previously, edit requests had a method called override(boolean) to replace the entire message. This was used to remove content or embeds from a message. You can now simply use setEmbeds(emptyList()) or setContent("") to remove specific parts of the message, or use setReplace(true) to achieve the same functionality of replacing everything.
Method Renames¶
Some methods were renamed to allow for consistency between all requests.
setActionRows/addActionRowsrenamed tosetComponents/addComponentsMessageAction#ttsrenamed toMessageCreateRequest#setTTSMessageAction#contentrenamed toMessageCreateRequest#setContentMessageAction#allowedMentionsrenamed toMessageRequest#setAllowedMentionsaddFileremoved in favor ofaddFilesandsetAttachments(see File Sending section below)
Unifying Message Requests¶
All message send and edit requests now use a unified MessageRequest interface. This allows you to make very abstracted implementations that use high level interfaces.
This also means that all methods from message.editMessage(...) are consistent with the methods from interaction.editMessage(...), and analogously for sending. You can now use addActionRow when sending a message to a channel.
File Sending¶
The old sendFile(...)/replyFile(...) overloads available on MessageChannel and interactions has been replaced by a single sendFiles(...) method. This new method accepts the FileUpload type, which also supports file descriptions (alt text) via setDescription(...). For example, old code such as sendFile(data, name, AttachmentOption.SPOILER) is replaced with sendFiles(FileUpload.fromData(data, name).asSpoiler()).
Due to changes in the Discord API v10, you can no longer add files to messages without replacing all existing attachments. We could previously support methods such as MessageAction#addFile for editing messages, which is no longer possible in API v10. Instead, you must replace the entire list of attachments, using MessageEditAction#setAttachments. Here is an example:
// Here "message" is an instance of the Message interface
// Take the first attachment of the message, all others will be removed
AttachedFile attachment = message.getAttachments().get(0);
// The name here will be "cat.png" to discord, what the file is called on your computer is irrelevant and only used to read the data of the image.
FileUpload file = FileUpload.fromData(new File("mycat-final-copy.png"), "cat.png"); // Opens the file called "cat.png" and provides the data used for sending
// Edit request to keep the first attachment, and add one more file to the message
message.editMessage("New content")
.setAttachments(attachment, file)
.queue();
Message Splitting¶
The old MessageBuilder#buildAll has been removed from the builder classes. Instead, you can now use the new SplitUtil utility class to split any string. For example:
Old:
List<Message> messages = new MessageBuilder().setContent(someLargeString).buildAll();
New:
List<String> contents = SplitUtil.split(
someLargeString, // input string of arbitrary length
2000, // the split limit, can be arbitrary (>0)
true, // whether to trim the strings (empty will be discarded)
Strategy.NEWLINE, // split on '\n' characters if possible
Strategy.ANYWHERE // otherwise split on the limit
);
// Convert to instance of MessageCreateData (optional, you can just send strings directly!)
List<MessageCreateData> messages = contents.stream().map(MessageCreateData::fromContent).toList();
Mentions Rework¶
We made the handling of mentions consistent across both messages and interactions. You can now access mentions inside string options of slash commands through OptionMapping#getMentions. The old methods on Message have also been moved to a new Mentions interface, accessible via Message#getMentions.
Message#getMentionedUsersmoved toMentions#getUsersMessage#getMentionedMembersmoved toMentions#getMembersMessage#getMentionedChannelsmoved toMentions#getChannelsMessage#getEmotesmoved toMentions#getCustomEmojisMessage#getMentions(MentionType...)moved toMentions#getMentions(MentionType...)Message#isMentionedmoved toMentions#isMentionedMessage#getMentionedMembers(Guild)has been removed with no replacementMessage#mentionsEveryonemoved toMentions#mentionsEveryone- Analogously for all bag getters.
Additionally, the return type of Mentions#getChannels has been adjusted to return List<GuildChannel> since all channel types can now be mentioned. You can use Mentions#getChannels(Class) to limit it to specific types, for example List<TextChannel> channels = mentions.getChannels(TextChannel.class).
Interaction Rework¶
To properly handle Context Menu and Auto-complete interactions, we reworked some interaction types. This includes numerous breaking changes to naming conventions and package layouts.
Naming Changes¶
SlashCommandEventrenamed toSlashCommandInteractionEventButtonClickEventrenamed toButtonInteractionEventSelectionMenurenamed toStringSelectMenuSelectionMenuEventrenamed toStringSelectEventComponentrenamed toActionComponentandItemComponent(abstraction for things likeButton)ComponentLayoutrenamed toLayoutComponent(abstraction for things likeActionRow)- Introduced new
Componentinterface to abstract both layouts and items. MessageType.APPLICATION_COMMANDrenamed toMessageType.SLASH_COMMAND(we now also haveMessageType.CONTEXT_COMMAND)InteractionType.SLASH_COMMANDrenamed toInteractionType.COMMAND(we now also haveInteractionType.COMMAND_AUTOCOMPLETE)SlashCommandEvent#getCommandPathrenamed toCommandInteractionPayload#getFullCommandName(it also now uses spaces instead of slashes, e.g.mod/banis nowmod ban)
Creating and Handling Commands¶
With the introduction of Context Menu Commands, we changed how commands are created. Instead of new CommandData(...), you now create a slash command using the factory method Commands.slash(...), which returns a SlashCommandData instance that has the familiar methods. You can now also use Commands.user(...) and Commands.message(...) to create new context menu commands which appear in the right-click menu option named Apps in the Discord Client.
Handling commands only changed slightly with regard to the way you reply. Previously, all Interaction types had a reply(...) or deferReply(...) method. This has been changed due to new interaction types like AutoCompleteInteraction and ModalInteraction. Now, each concrete interaction type implements specific callback interfaces such as:
IReplyCallback
Which supports direct message replies and deferred message replies viareply(String)anddeferReply()IMessageEditCallback
Which supports direct message edits and deferred message edits (or no-operation) viaeditMessage(String)anddeferEdit()IModalCallback
Which supports replying using a Modal viareplyModal(Modal)IAutoCompleteCallback
Which supports choice suggestions for auto-complete interactions viareplyChoices(Command.Choice...)
If you relied on the abstract Interaction type to provide any of these methods, you will have to adjust your code to use these new interfaces instead.
You can find more explanations and examples in the dedicated Interactions Wiki Page.
Command Permissions/Privileges¶
Discord has changed how command permissions work. Instead of limiting commands to specific roles and users on a per-guild basis, you set required permissions on each command. For instance, a ban command would require Permission.BAN_MEMBERS:
commands.addCommands(
Commands.slash("ban", "Ban a user from this server. Requires permission to ban users.")
.addOption(USER, "user", "The user to ban", true)
.addOptions(new OptionData(INTEGER, "del_days", "Delete messages from the past days.")
.setRequiredRange(0, 7)) // Only allow values between 0 and 7 (inclusive)
.addOptions(new OptionData(STRING, "reason", "The ban reason to use (default: Banned by <user>)"))
// This way the command can only be executed from a guild, and not DMs
.setGuildOnly(true)
// Only members with the BAN_MEMBERS permission are going to see this command
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.BAN_MEMBERS))
)
You can also limit it to administrators with DefaultMemberPermissions.DISABLED.
UserSnowflake Type¶
We have started to cut down on overloads which accept 3 different inputs to specify a user. Previously methods like Guild#ban had many overloads to account for different types such as String/long/User/Member. We removed all of these in favor of a single UserSnowflake input, which can be constructed with User.fromId(long/String). Both User and Member also implement this interface. This is a far more elegant handling and reduces the number of overloads in various parts of the API drastically.
This applies to the following methods:
Guild#ban(long/String),Guild#ban(long/String, int),Guild#ban(long/String, int, String)
Now justGuild#ban(UserSnowflake, int, TimeUnit)Guild#kick(long/String),Guild#kick(long/String, String)
Now justGuild#kick(UserSnowflake)Guild#unban(long/String)is nowGuild#unban(UserSnowflake)Guild#retrieveBanById(long/String)is nowGuild#retrieveBan(UserSnowflake)Guild#addMember(String, long/String)is nowGuild#addMember(UserSnowflake)Guild#timeoutForById(long/String, Duration)is nowGuild#timeoutFor(UserSnowflake, Duration)Guild#timeoutUntilById(long/String, TemporalAccessor)is nowGuild#timeoutUntil(UserSnowflake, TemporalAccessor)Guild#removeTimeoutById(long/String)is nowGuild#removeTimeout(UserSnowflake)Guild#addRoleToMember(long/String, Role)is nowGuild#addRoleToMember(UserSnowflake)Guild#removeRoleFromMember(long/String, Role)is nowGuild#removeRoleFromMember(UserSnowflake)AuditLogPaginationAction#user(long/String)is nowAuditLogPaginationAction#user(UserSnowflake)
Ban Precision¶
You can now specify the age of messages to delete in seconds precision. To adjust your code simply add TimeUnit.DAYS to the end and move the ban reason into the reason(...) method:
Old:
guild.ban(user, 7, "Naughty words").queue()
New:
guild.ban(user, 7, TimeUnit.DAYS).reason("Naughty words").queue()
Kick reasons have also been moved into the reason(...) method, for example guild.kick(user, reason) turns into guild.kick(user).reason(reason).
Managers are no longer persistent¶
All getters for managers in JDA used to be lazy-idempotent, such that calling getManager() twice would return the same instance. This has been removed to reduce memory usage and complexity. If you previously relied on this behavior, you will need to adjust your code.
Old:
channel.getManager().setName("new-name");
channel.getManager().setTitle("here is my title");
channel.getManager().queue();
New:
// Use method chaining or declare a variable to re-use the manager
channel.getManager()
.setName("new-name")
.setTitle("here is my title")
.queue();