JDA strives to provide a clean and full wrapping of the Discord REST API and its WebSocket-Events for Java. This library is a helpful tool that provides the functionality to create a Discord bot in Java.
- Introduction
- Sharding
- Entity Lifetimes
- Download
- Documentation
- Support
- Extensions And Plugins
- Contributing
- Dependencies
- Other Libraries
Discord is currently prohibiting creation and usage of automated client accounts (AccountType.CLIENT). We have officially dropped support for client login as of version 4.2.0! If you need a bot, use a bot account from the Application Dashboard.
Creating the JDA Object is done via the JDABuilder class. After setting the token and other options via setters,
the JDA Object is then created by calling the build()
method. When build()
returns,
JDA might not have finished starting up. However, you can use awaitReady()
on the JDA object to ensure that the entire cache is loaded before proceeding.
Note that this method is blocking and will cause the thread to sleep until startup has completed.
Example:
JDA jda = JDABuilder.createDefault("token").build();
Both the JDABuilder
and the DefaultShardManagerBuilder
allow a set of configurations to improve the experience.
Example:
public static void main(String[] args) {
JDABuilder builder = JDABuilder.createDefault(args[0]);
// Disable parts of the cache
builder.disableCache(CacheFlag.MEMBER_OVERRIDES, CacheFlag.VOICE_STATE);
// Enable the bulk delete event
builder.setBulkDeleteSplittingEnabled(false);
// Set activity (like "playing Something")
builder.setActivity(Activity.watching("TV"));
builder.build();
}
See JDABuilder and DefaultShardManagerBuilder
You can configure the memory usage by changing enabled CacheFlags
on the JDABuilder
.
Additionally, you can change the handling of the member/user cache by disabling intents or changing the member cache policy.
To learn more about intents and member loading/caching, read the Gateway Intents Guide.
public void configureMemoryUsage(JDABuilder builder) {
// Disable cache for member activities (streaming/games/spotify)
builder.disableCache(CacheFlag.ACTIVITY);
// Only cache members who are either in a voice channel or owner of the guild
builder.setMemberCachePolicy(MemberCachePolicy.VOICE.or(MemberCachePolicy.OWNER));
// Disable member chunking on startup
builder.setChunkingFilter(ChunkingFilter.NONE);
// Disable presence updates and typing events
builder.disableIntents(GatewayIntent.GUILD_PRESENCE, GatewayIntent.GUILD_MESSAGE_TYPING);
// Consider guilds with more than 50 members as "large".
// Large guilds will only provide online members in their setup and thus reduce bandwidth if chunking is disabled.
builder.setLargeThreshold(50);
}
The event system in JDA is configured through a hierarchy of classes/interfaces.
We offer two implementations for the IEventManager
:
- InterfacedEventManager which uses an
EventListener
interface and theListenerAdapter
abstract class - AnnotatedEventManager which uses the
@SubscribeEvent
annotation which can be applied to methods
By default, the InterfacedEventManager is used.
Since you can create your own implementation of IEventManager
, this is a very versatile and configurable system.
If the aforementioned implementations don't suit your use-case you can simply create a custom implementation and
configure it on the JDABuilder
with setEventManager(...)
.
Using EventListener:
public class ReadyListener implements EventListener {
public static void main(String[] args) throws InterruptedException {
// Note: It is important to register your ReadyListener before building
JDA jda = JDABuilder.createDefault("token")
.addEventListeners(new ReadyListener())
.build();
// optionally block until JDA is ready
jda.awaitReady();
}
@Override
public void onEvent(GenericEvent event) {
if (event instanceof ReadyEvent) {
System.out.println("API is ready!");
}
}
}
Using ListenerAdapter:
public class MessageListener extends ListenerAdapter {
public static void main(String[] args) {
JDA jda = JDABuilder.createDefault("token")
.enableIntents(GatewayIntent.MESSAGE_CONTENT) // enables explicit access to message.getContentDisplay()
.build();
//You can also add event listeners to the already built JDA instance
// Note that some events may not be received if the listener is added after calling build()
// This includes events such as the ReadyEvent
jda.addEventListener(new MessageListener());
}
@Override
public void onMessageReceived(MessageReceivedEvent event) {
if (event.isFromType(ChannelType.PRIVATE)) {
System.out.printf("[PM] %s: %s\n", event.getAuthor().getName(),
event.getMessage().getContentDisplay());
} else {
System.out.printf("[%s][%s] %s: %s\n", event.getGuild().getName(),
event.getTextChannel().getName(), event.getMember().getEffectiveName(),
event.getMessage().getContentDisplay());
}
}
}
Slash-Commands:
public class Bot extends ListenerAdapter {
public static void main(String[] args) {
if (args.length < 1) {
System.out.println("You have to provide a token as first argument!");
System.exit(1);
}
// args[0] would be the token (using an environment variable or config file is preferred for security)
// We don't need any intents for this bot. Slash commands work without any intents!
JDA jda = JDABuilder.createLight(args[0], Collections.emptyList())
.addEventListeners(new Bot())
.setActivity(Activity.playing("Type /ping"))
.build();
// Sets the global command list to the provided commands (removing all others)
jda.updateCommands().addCommands(
Commands.slash("ping", "Calculate ping of the bot"),
Commands.slash("ban", "Ban a user from the server")
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.BAN_MEMBERS)) // only usable with ban permissions
.setGuildOnly(true) // Ban command only works inside a guild
.addOption(OptionType.USER, "user", "The user to ban", true) // required option of type user (target to ban)
.addOption(OptionType.STRING, "reason", "The ban reason") // optional reason
).queue();
}
@Override
public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
// make sure we handle the right command
switch (event.getName()) {
case "ping":
long time = System.currentTimeMillis();
event.reply("Pong!").setEphemeral(true) // reply or acknowledge
.flatMap(v ->
event.getHook().editOriginalFormat("Pong: %d ms", System.currentTimeMillis() - time) // then edit original
).queue(); // Queue both reply and edit
break;
case "ban":
// double check permissions, don't trust Discord on this!
if (!event.getMember().hasPermission(Permission.BAN_MEMBERS)) {
event.reply("You cannot ban members! Nice try ;)").setEphemeral(true).queue();
break;
}
User target = event.getOption("user", OptionMapping::getUser);
// optionally check for member information
Member member = event.getOption("user", OptionMapping::getMember);
if (!event.getMember().canInteract(member)) {
event.reply("You cannot ban this user.").setEphemeral(true).queue();
break;
}
// Before starting our ban request, tell the user we received the command
// This sends a "Bot is thinking..." message which is later edited once we finished
event.deferReply().queue();
String reason = event.getOption("reason", OptionMapping::getAsString);
AuditableRestAction<Void> action = event.getGuild().ban(target, 0); // Start building our ban request
if (reason != null) // reason is optional
action = action.reason(reason); // set the reason for the ban in the audit logs and ban log
action.queue(v -> {
// Edit the thinking message with our response on success
event.getHook().editOriginal("**" + target.getAsTag() + "** was banned by **" + event.getUser().getAsTag() + "**!").queue();
}, error -> {
// Tell the user we encountered some error
event.getHook().editOriginal("Some error occurred, try again!").queue();
error.printStackTrace();
});
break;
default:
System.out.printf("Unknown command %s used by %#s%n", event.getName(), event.getUser());
}
}
}
Through RestAction we provide request handling with
and it is up to the user to decide which pattern to utilize. It can be combined with reactive libraries such as reactor-core due to being lazy.
The RestAction interface also supports a number of operators to avoid callback hell:
map
Convert the result of theRestAction
to a different valueflatMap
Chain anotherRestAction
on the resultdelay
Delay the element of the previous step
Example:
public RestAction<Void> selfDestruct(MessageChannel channel, String content) {
return channel.sendMessage("The following message will destroy itself in 1 minute!")
.delay(10, SECONDS, scheduler) // edit 10 seconds later
.flatMap((it) -> it.editMessage(content))
.delay(1, MINUTES, scheduler) // delete 1 minute later
.flatMap(Message::delete);
}
We provide a small set of Examples in the Example Directory.
When your bot joins over 2500 guilds, it is required to perform Sharding. This means, your connection is split up into multiple shards, each only accessing a fraction of your total available guilds. A shard can at most contain 2500 guilds when starting up the bot.
Each shard is assigned a shard id and shard total (usually shown as id / total
), which uniquely identifies which guilds are accessible on that shard.
For instance, the first of 2 shards would be 0 / 2
and the second would be 1 / 2
.
If you want to use sharding with your bot, make use of the DefaultShardManager as seen in the example below.
This manager automatically assigns the right number of shards to your bot, so you do not need to do any math yourself.
The SessionController
is a tool of the JDABuilder
that allows to control state and behaviour between shards (sessions). When using multiple builders to build shards you have to create one instance
of this controller and add the same instance to each builder: builder.setSessionController(controller)
Internally, this shard manager also handles the proper scaling of threads for connections and handles the login rate-limit (Identify Rate-Limit) to properly startup without issues.
If you do not want to use the shard manager, and instead manage sharding yourself, you can use JDABuilder#useSharding and ConcurrentSessionController.
public static void main(String[] args) {
DefaultShardManagerBuilder builder = DefaultShardManagerBuilder.createDefault(args[0]);
builder.addEventListeners(new MessageListener());
builder.build();
}
An Entity is the term used to describe types such as GuildChannel/Message/User and other entities that Discord provides. Instances of these entities are created and deleted by JDA when Discord instructs it. This means the lifetime depends on signals provided by the Discord API which are used to create/update/delete entities. This is done through Gateway Events known as "dispatches" that are handled by the JDA WebSocket handlers. When Discord instructs JDA to delete entities, they are simply removed from the JDA cache and lose their references. Once that happens, nothing in JDA interacts or updates the instances of those entities, and they become outdated. Discord may instruct to delete these entities randomly for cache synchronization with the API.
It is not recommended to store any of these entities for a longer period of time!
Instead of keeping (e.g.) a User
instance in some field, an ID should be used. With the ID of a user,
you can use getUserById(id)
to get and keep the user reference in a local variable (see below).
When an entity is updated through its manager, they will send a request to the Discord API which will update the state of the entity. The success of this request does not imply the entity has been updated yet. All entities are updated by the aforementioned Gateway Events which means you cannot rely on the cache being updated yet once the execution of a RestAction has completed. Some requests rely on the cache being updated to correctly update the entity. An example of this is updating roles of a member which overrides all roles of the member by sending a list of the new set of roles. This is done by first checking the current cache, the roles the member has right now, and appending or removing the requested roles. If the cache has not yet been updated by an event, this will result in unexpected behavior.
Discord may request that a client (the JDA session) invalidates its entire cache. When this happens, JDA will remove all of its current entities and reconnect the session. This is signaled through the SessionRecreateEvent
. When entities are removed from the JDA cache, your instance will keep stale entities in memory. This results in memory duplication, potential memory leaks, and outdated state. It is highly recommended to only keep references to entities by storing their id and using the respective get...ById(id)
method when needed. Alternatively, keep the entity stored and make sure to replace it as soon as possible when the cache is replaced.
public class UserLogger extends ListenerAdapter {
private final User user;
public UserLogger(User user) {
this.user = user;
}
private User getUser(JDA api) {
// Acquire a reference to the User instance through the id
User newUser = api.getUserById(this.user.getIdLong());
if (newUser != null)
this.user = newUser;
return this.user;
}
@Override
public void onMessageReceived(MessageReceivedEvent event) {
User author = event.getAuthor();
Message message = event.getMessage();
if (author.getIdLong() == this.user.getIdLong()) {
// Update user from message instance (likely more up-to-date)
this.user = author;
// Print the message of the user
System.out.println(author.getAsTag() + ": " + message.getContentDisplay());
}
}
@Override
public void onGuildJoin(GuildJoinEvent event) {
JDA api = event.getJDA();
User user = getUser(); // use getter to refresh user automatically on access
user.openPrivateChannel().queue((channel) -> {
// Send a private message to the user
channel.sendMessageFormat("I have joined a new guild: **%s**", event.getGuild().getName()).queue();
});
}
}
Latest Release: GitHub Release
Be sure to replace the VERSION key below with the one of the versions shown above! For snapshots, please use the instructions provided by JitPack.
Maven
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>VERSION</version>
</dependency>
Maven without Audio
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>VERSION</version>
<exclusions>
<exclusion>
<groupId>club.minnced</groupId>
<artifactId>opus-java</artifactId>
</exclusion>
</exclusions>
</dependency>
Gradle
repositories {
mavenCentral()
}
dependencies {
//Change 'implementation' to 'compile' in old Gradle versions
implementation("net.dv8tion:JDA:VERSION")
}
Gradle without Audio
dependencies {
//Change 'implementation' to 'compile' in old Gradle versions
implementation("net.dv8tion:JDA:VERSION") {
exclude module: 'opus-java'
}
}
The snapshot builds are only available via JitPack and require adding the JitPack resolver, you need to specify specific commits to access those builds. Stable releases are published to maven-central.
If you do not need any opus de-/encoding done by JDA (voice receive/send with PCM) you can exclude opus-java
entirely.
This can be done if you only send audio with an AudioSendHandler
which only sends opus (isOpus() = true
). (See lavaplayer)
If you want to use a custom opus library you can provide the absolute path to OpusLibrary.loadFrom(String)
before using
the audio API of JDA. This works without opus-java-natives
as it only requires opus-java-api
.
For this setup you should only exclude opus-java-natives
as opus-java-api
is a requirement for en-/decoding.
See opus-java
JDA is using SLF4J to log its messages.
That means you should add some SLF4J implementation to your build path in addition to JDA. If no implementation is found, following message will be printed to the console on startup:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
JDA currently provides a fallback Logger in case that no SLF4J implementation is present. We strongly recommend to use one though, as that can improve speed and allows you to customize the Logger as well as log to files
There is a guide for logback-classic available in our wiki: Logging Setup
Docs can be found on the GitHub Pages
We also have a wiki filled with information and troubleshooting guides at jda.wiki
Docs can be found on the Jenkins or directly here
A simple Wiki can also be found at jda.wiki
We use a number of annotations to indicate future plans for implemented functionality such as new features of the Discord API.
- Incubating
This annotation is used to indicate that functionality may change in the future. Often used when a new feature is added. - ReplaceWith
Paired with@Deprecated
this is used to inform you how the new code-fragment is supposed to look once the hereby annotated functionality is removed. - ForRemoval
Paired with@Deprecated
this indicates that we plan to entirely remove the hereby annotated functionality in the future. - DeprecatedSince
Paired with@Deprecated
this specifies when a feature was marked as deprecated.
For general troubleshooting you can visit our wiki Troubleshooting and FAQ.
If you need help, or just want to talk with the JDA or other Devs, you can join the Official JDA Discord Guild.
Alternatively you can also join the Unofficial Discord API Guild.
Once you joined, you can find JDA-specific help in the #java_jda
channel.
For guides and setup help you can also take a look at the wiki
Especially interesting are the Getting Started
and Setup Pages.
Created by sedmelluq and now maintained by the lavalink community
Lavaplayer is the most popular library used by Music Bots created in Java.
It is highly compatible with JDA and Discord4J and allows to play audio from
Youtube, Soundcloud, Twitch, Bandcamp and more providers.
The library can easily be expanded to more services by implementing your own AudioSourceManager and registering it.
It is recommended to read the Usage section of Lavaplayer
to understand a proper implementation.
Sedmelluq provided a demo in his repository which presents an example implementation for JDA:
https://github.com/lavalink-devs/lavaplayer/tree/master/demo-jda
Created by Freya Arbjerg and now maintained by the lavalink community.
Lavalink is a popular standalone audio sending node based on Lavaplayer. Lavalink was built with scalability in mind, and allows streaming music via many servers. It supports most of Lavaplayer's features.
Lavalink is used by many large bots, as well as bot developers who can not use a Java library like Lavaplayer. If you plan on serving music on a smaller scale with JDA it is often preferable to just use Lavaplayer directly as it is easier.
Lavalink-Client is the official Lavalink client for JDA.
Created and maintained by sedmelluq and extended by MinnDevelopment
Provides a native implementation for the JDA Audio Send-System to avoid GC pauses.
Note that this send system creates an extra UDP-Client which causes audio receive to no longer function properly, since Discord identifies the sending UDP-Client as the receiver.
JDABuilder builder = JDABuilder.createDefault(BOT_TOKEN)
.setAudioSendFactory(new NativeAudioSendFactory());
Created and maintained by MinnDevelopment.
Provides Kotlin extensions for RestAction and events that provide a more idiomatic Kotlin experience.
fun main() {
val jda = light(BOT_TOKEN)
jda.onCommand("ping") { event ->
val time = measureTime {
event.reply("Pong!").await() // suspending
}.inWholeMilliseconds
event.hook.editOriginal("Pong: $time ms").queue()
}
}
There is a number of examples available in the README.
More can be found in our github organization: JDA-Applications
If you want to contribute to JDA, make sure to base your branch off of our master branch (or a feature-branch) and create your PR into that same branch.
More information can be found at the wiki page Contributing.
Since the Discord API is in itself a moving standard, the stability is never guaranteed. For this reason, JDA does not follow the common semver versioning strategy.
The JDA version is structured with a looser definition, where the version change indicates the significance of changes.
For instance, using 5.1.2
as a baseline:
- A change to the major like
6.0.0
indicates that a lot of code has to be adjusted due to major changes to the interfaces. A change like this always comes with a full migration guide like Migrating from 4.X to 5.X. - A change to the minor like
5.2.0
indicates some code may need to be adjusted due to the removal or change of interfaces. You can usually find the necessary changes in the release documentation. - A change to the patch like
5.1.3
indicates bug fixes and new feature additions that are backwards compatible.
If a feature is marked as deprecated, it usually also indicates an alternative. For instance:
@Deprecated
@DeprecatedSince("5.1.2")
@ForRemoval(deadline="5.2.0")
@ReplaceWith("setFoo(foo)")
public void changeFoo(Foo foo) { ... }
The method changeFoo
was deprecated in release 5.1.2
and is going to be removed in 5.2.0
. Your change should replace all usage of changeFoo(foo)
with setFoo(foo)
.
Sometimes, a feature might be removed without a replacement. This will be clearly explained in the documentation.
The RR
in version 3.4.RR
should be replaced by the latest version that was published for 3.4
, you can find out which the latest
version was by looking at the release page
This project requires Java 8+.
All dependencies are managed automatically by Gradle.
- NV WebSocket Client
- Version: 2.14
- Github
- OkHttp
- Version: 4.10.0
- Github
- Apache Commons Collections4
- Version: 4.4
- Website
- jackson
- Version: 2.14.1
- Github
- Trove4j
- Version: 3.0.3
- BitBucket
- slf4j-api
- Version: 1.7.36
- Website
- opus-java (optional)
- Version: 1.1.1
- GitHub
See also: Discord API Community Libraries