Discord4J v3 is a completely different programming paradigm compared to v2. Rather than it being focused around synchronous, blocking invocations; everything is handled in an asynchronous, reactive context. The API has also been completely refactored, allowing D4J to provide a much cleaner, richer, consistent, and flexible approach to bot development.
The core of Discord4J's design is centered around reactive programming, using Reactor as its implementation. This is primarily focused around 2 classes,
Flux are designed for asynchronous computations, they do offer synchronous conversions for more traditional imperative programming that will be familiar to v2 developers.
Blocking completely eliminates any and all benefits of reactive programming. We highly recommend that you learn more about reactive programming and eventually convert your code to be more reactive after the initial migration for better performance and scalability.
Any method that returns a
Flux must be "subscribed" to in order for an action to be performed. This is vastly different compared to v2, where simply invoking the method instantly caused the method to execute. To mimic that behavior, we can simply call
Flux can be converted to a
Mono is a significantly more powerful version of
RestAction. It provides both an analog to
block) while additionally providing more operations for easier and more generic handling of data and actions both synchronously and asynchronously.
Mono is a significantly more powerful version of
CompletableFuture. It provides a more concise, standard, and easier manipulation of data and actions asynchronously compared to
CompletableFuture's copious amounts of
handle methods and still provides an analog to
Mono#block. In fact, a
Mono can be converted to and from a
EventDispatcher has been reworked and
IListener (and consequently
@EventSubscriber) has been completely removed in v3. To "listen" for an event, simply call
EventDispatcher#on and subscribe for its contents:
IListener's functionality you may use the following example:
ReadyEvent in v3 now represents Discord's ReadyEvent, which is sent before any
GuildCreateEvent. This is different compared to v2 where
ReadyEvent was defined when the bot was "ready", meaning all guilds have been received. In exchange, however, v3 does not require the bot to be "ready" to execute any actions (such as sending a message). This, consequently, also means v3 does not require the bot to be logged in to perform actions to Discord.
To mimic v2's
ReadyEvent, i.e. know when all guilds have been received, you may use the following example:
Everything. Its problems stem from being the oldest of the 3 major libraries (written before a time the bot API existed) and its developer(s) having inadequate knowledge of Java conventions/practices and lacking OOP concepts.
v2 is a completely blocking API. This means threads must "wait" for actions to be completed before continuing. This wastes a tremendous amount of resources at scale as threads could be accomplishing various other tasks as they "wait" for previous tasks to finish. As noted by this chart, even wasting a millisecond, in relative terms, is a huge waste of time for a computer, and a typical Discord request is about 50 milliseconds. v3, thanks to Reactor, can maximize the usage of this wasted computing power to utilize less resources to accomplish more tasks.
Of course, you do not have to utilize Reactor's asynchronous features. As previously discussed, blocking with Reactor can be easily accomplished to achieve the previous paradigm.
Channel hierarchy is both wrong and inconsistent.
IChannelcan be represented either by a guild's text channel or a privately messaged channel; meaning methods like
IChannel#getGuildmake no fundamental sense if the type of the channel is private. Should the method return null, or throw an exception? This ambiguity is made worse by the fact
IVoiceChannelconceptually represents a guild's text channel, a privately messaged channel, and a guild voice channel! Most methods in
IVoiceChannelthrow an exception as they make no sense in the context of an actual voice channel (you cannot send a message in a voice channel, for instance). Additionally, categories are channels, but in v2 they are not represented this way via
ICategory. v3 fixes all these issues with a much better entity hierarchy structure.
RequestBuilderwere workarounds for rate limiting when it was introduced (yes, v2 was built before rate limiting was a concept for Discord). This makes their usage cumbersome as they should be applied to every possible request to Discord. This makes knowing when to use them entirely unclear (which methods, exactly, should either be applied to?), and its existence not balancing its requirement for bot development as a lot of users do not realize these two features exist. v3 fixes this as rate limits will be automatically handled and requests will be executed in order.
The MessageHistory API is confusing as its construction is spread out across 15 different methods with unexpected, unorthodox, and/or confusing naming and behaviors. Additionally, since
List, all messages must be obtained before manipulating them; meaning for very large message histories, it is very likely to reach an OutOfMemoryError when attempting to obtain a history of these channels. v3, in contrast, only has 2 methods to obtain a "message history", with all of the functionality of v2's message history being applicable and more. Additionally, because of Reactor, messages can be retrieved on-demand so not all messages have to be loaded in memory before utilizing them for some purpose (like bulk-delete).
v2 is riddled with inconsistencies across its API. Some methods return
null, others throw an exception, while others return
Optional. v3 has been heavily focused on staying consistent across its entire API to prevent any unexpected behaviors or lopsided functionality. If something can be "absent", it'll return
Optional. If it can make a request to Discord, it returns either a
Flux. There are no surprises on what a method may attempt to accomplish or inconsistencies with handling specific cases.
Manipulating entities in v2 is both inefficient and cumbersome. Most entities when being created or edited can set multiple properties at once. For instance, when you create a channel you can set the name, type, position, permission overwrites, etc. all in one request, however, in v2 this is impossible. In order to create/edit with multiple properties you must call individual methods one at a time which makes an entire request to Discord on each and every single invocation. This is tremendously wasteful and quickly makes your bot approach a rate limit. v3 fixes this by utilizing Specs.
While it is "possible" to disable the cache in v2, it instantly causes a crash on startup when attempted. v3 was designed with caches being disabled in mind, allowing very lightweight configurations if desired. We have tested v3 running on some of Tatsumaki's shards, and v3 was able to stay under 10 MB of RAM usage. v3's Store API is also far more flexible, allowing other configurations such as off-heap caching to be possible.
v2 is completely mutable which is susceptible to many race conditions that are incredibly hard to replicate, track down, and/or fix. v3 attempts to be as immutable as possible which has numerous benefits for us as a library and you as a user.
v2 follows some unusual conventions.
Iprefixes for interfaces (which is a C# convention, but not a Java one), as well as a questionable package hierarchy structure (most events are under
impl, for example). v3's follows proper Java industry conventions, and a package hierarchy that makes intuitive sense.
RequestBuilder have been completely removed in v3. By default, v3 will execute requests in order and handle rate limits.
Important: This section focuses on Discord4J v3.1 rather than v3.0.
- The main utility class
Discord4Jwas removed. If you used
Discord4J::enableJettyLoggingyou should read up on Logging.
DiscordExceptionis not used anymore, and you should attempt to migrate towards reactive Error handling or if you decide to block, catch
- If your login flow consisted in obtaining an
IDiscordClientinstance, you should now either expect
ClientBuilderis now replaced with
DiscordClientBuilder. Several options don't exist anymore or have replacements elsewhere. It comes with default options to allow a monolithic multi-shard bot to function. You can start with
ClientBuilder::setDaemondoes not have an equivalent as it uses Reactor threading. Check the Threading page for more information.
ClientBuilder::withPingTimeoutis now called
setMaxMissedHeartbeatAckand can be set at
ClientBuilder::setMaxReconnectAttemptsnow live under
ReconnectOptionswhich can be set at
DiscordClient::gateway. See v3.1 Migration Guide
- Shard options like
::withRecommendedShardCountare present under
DiscordClient::gateway. See v3.1 Migration Guide
ClientBuildercache-related options like
setCacheProvidernow live under the Stores abstraction and can be replaced using a combination of
CaffeineStoreService. These options are set under
DiscordClient::gateway. See also Stores-Caffeine
ClientBuilder::registerListenervariants are moved to
ClientBuilder::set5xxRetryCountis now abstracted to
DiscordClientBuilder::onClientResponsewhere you can set a custom retrying policy. Discord4J retries most 5xx errors by default.
- Event processing options now live under
EventDispatcherabstraction and can be replaced at
EventDispatcherclass for some built-in factories.
- Setting an initial presence/status is done at
DiscordClient::logindirectly uses all defaults for Gateway. To customize go through
DiscordClient::gateway, customize the given
GatewayBootstrapand then call
GatewayDiscordClient::onto attach a subscriber to receive all events for that type.
- If you relied heavily upon
@EventSubscriberfrom v2 you can create an
EventSubscriberAdapterclass yourself and use it in the following way:
@EventSubscriber was removed you can use that pattern, overriding the methods you wish to get notified. There is also a reactive alternative
ReactiveEventAdapter you can use similarly.
- If you previously used
IListener<E>you can also migrate it to
- Events now live under the
DisconnectedEventhas split into
- In general, if an
Flux, it means it involves a request that might incur latency and therefore we use Reactor to properly route and schedule that action asynchronously. Such methods are safe to call
Flux::blockLastupon to use the contained type in a blocking way.
- All entities have different names now, but the correlation should be easy to follow, for example:
- Checking if a
MessageChannelis private can be done through
instanceof PrivateChannelor using
getTypeafter blocking, or
GatewayDiscordClient::getSelfand optionally block
Consider the following call:
To migrate this you should know:
- To work with
EnumSet<Permission>. Build one using
Mono<MessageChannel>and calls like this only make sense under Guild channels, you must ensure first the actual type is
- If you block such a
Monoand the underlying channel is not a guild one, it will return
null. There is also
Mono::blockOptionalto get an
- After blocking, you can then call
Snowflakefor a member or role
The resulting code should look like: