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 newChannel
type 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 asVoiceChannel
andTextChannel
.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 asMessageChannel
which 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#setNews
is replaced byChannelAction.setType
ChannelManager#getType
is replaced byChannelManager#getChannel()#getType
ChannelManager#setNews
is replaced byChannelManager#setType
TextChannel#crosspostMessage
is replaced byNewsChannel#crosspostMessage
TextChannel#follow
is replaced byNewsChannel#follow
TextChannel#isNews
was removed
Other Changes¶
StoreChannel
was removedPrivateChannel#getUser
is now nullableMessageChannel#getLatestMessageId
andMessageChannel#getLatestMessageIdLong
no 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_EMOTES
renamed toPermission.MANAGE_EMOTES_AND_STICKERS
Permission.MESSAGE_WRITE
renamed toPermission.MESSAGE_SEND
Permission.USE_SLASH_COMMANDS
renamed toPermission.USE_APPLICATION_COMMANDS
Permission.USE_PUBLIC_THREADS
renamed toPermission.CREATE_PUBLIC_THREADS
Permission.USE_PRIVATE_THREADS
renamed toPermission.CREATE_PRIVATE_THREADS
Permission.MESSAGE_READ
was 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:
GenericMessageEvent
MessageReceivedEvent
MessageUpdatedEvent
MessageDeletedEvent
MessageReaction<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:
DisconnectEvent
renamed toSessionDisconnectEvent
ReconnectedEvent
renamed toSessionRecreateEvent
ResumedEvent
renamed 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_BANS
renamed toGatewayIntent.GUILD_MODERATION
GatewayIntent.GUILD_EMOJIS
renamed 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:
Emote
renamed toRichCustomEmoji
EmoteManager
renamed toCustomEmojiManager
MessageReactionRemoveEmoteEvent
renamed 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 concreteUnicodeEmoji
type instance.Emoji.fromCustom
Creates aCustomEmoji
instance 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 aEmojiUnion
instance, 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:
MessageCreateBuilder
MessageCreateAction
MessageEditBuilder
MessageEditAction
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
/addActionRows
renamed tosetComponents
/addComponents
MessageAction#tts
renamed toMessageCreateRequest#setTTS
MessageAction#content
renamed toMessageCreateRequest#setContent
MessageAction#allowedMentions
renamed toMessageRequest#setAllowedMentions
addFile
removed in favor ofaddFiles
andsetAttachments
(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#getMentionedUsers
moved toMentions#getUsers
Message#getMentionedMembers
moved toMentions#getMembers
Message#getMentionedChannels
moved toMentions#getChannels
Message#getEmotes
moved toMentions#getCustomEmojis
Message#getMentions(MentionType...)
moved toMentions#getMentions(MentionType...)
Message#isMentioned
moved toMentions#isMentioned
Message#getMentionedMembers(Guild)
has been removed with no replacementMessage#mentionsEveryone
moved 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¶
SlashCommandEvent
renamed toSlashCommandInteractionEvent
ButtonClickEvent
renamed toButtonInteractionEvent
SelectionMenu
renamed toStringSelectMenu
SelectionMenuEvent
renamed toStringSelectEvent
Component
renamed toActionComponent
andItemComponent
(abstraction for things likeButton
)ComponentLayout
renamed toLayoutComponent
(abstraction for things likeActionRow
)- Introduced new
Component
interface to abstract both layouts and items. MessageType.APPLICATION_COMMAND
renamed toMessageType.SLASH_COMMAND
(we now also haveMessageType.CONTEXT_COMMAND
)InteractionType.SLASH_COMMAND
renamed toInteractionType.COMMAND
(we now also haveInteractionType.COMMAND_AUTOCOMPLETE
)SlashCommandEvent#getCommandPath
renamed toCommandInteractionPayload#getFullCommandName
(it also now uses spaces instead of slashes, e.g.mod/ban
is 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();