ℹ️ For a more detailed source of information, please refer to this section of the Reactor reference guide.
According to reactive streams specification, errors are terminal signals. This typically means any running sequence will be terminated and the error propagated to all operators down the chain.
The following are some valid strategies for dealing with errors:
Error will propagate downstream until the end of the chain and then run the
onError callback in your subscriber.
You can optionally log and react on the side using
As good practice, we encourage you to implement the
onError callback when subscribing to be properly notified. If you don't override the
onError callback, you will receive a Reactor
ErrorCallbackNotImplemented exception wrapping your original exception.
⚠️ Taking this approach under
EventDispatcher sequences will terminate your subscription, missing all further events for that subscriber. In general, this is a poor solution if you wish to perform extra behavior like starting another chain.
Catch and return a static default value:
✔️ This approach is good for individual API requests when you want to return a value in case of an error.
⚠️ Taking this approach with
EventDispatcher will terminate the sequence, and no further events will be received by that subscriber, unless you apply this operator to an inner sequence, without affecting the outer one containing all events, like this example:
onErrorReturn has overloads to include a condition so you could for example use the following to only recover from
404 status errors (Not found):
✔️ This approach is good for individual API requests when you want to provide alternative behavior.
✔️ This approach is great when working inside a
EventDispatcher, as you will replace the sequence with, for example,
Mono.empty() effectively suppresing the error while maintaining the original sequence.
If you were to place
onErrorResume outside a
flatMap, you'll replace the sequence, potentially missing some elements being processed:
Catch and Rethrow:
✔️ This approach is good for individual API requests when you want to translate the error, typically
ClientException, to a type you control for additional behavior downstream.
✔️ This approach is good when working with
EventDispatcher for the same reason as above. Be aware that the sequence is still on error and can be handled by a different strategy on following operators.
Error will terminate the original sequence, but
retry() (and variants) will re-subscribe to the upstream Flux. Be aware that this ultimately means that a new sequence is created.
⚠️ This approach is generally appropriate for API requests, but there are certain errors you should not retry. By default, Discord4J retries some errors for you, using an exponential backoff with jitter strategy.
✔️ This approach is compatible with
EventDispatcher sources when using the default event processor, due to it only relaying events since the time of subscription. As retrying creates a new subscription, the erroring event will be discarded and the sequence will continue from the next event.
⚠️ This only works on supporting operators:
filter, among others according to their javadocs. This operator goes beyond the Reactive Streams spec and uses the Reactor Context to work, therefore it is prone to issues when combining it with unsupported operators. Only use this operator if you understand the consequences, or you're familiar with how Reactor Context works.
onErrorContinue on a
Flux will change the default behavior of treating errors as terminal events to discarding erroneous elements and keeping the same sequence active.
If an error occurs, supporting operators will look for the continue strategy flag before reaching other operators.
Mono sequences, we generally recommend sticking with resuming if you already use it, unless you want a catch-all behavior that can override other
onErrorStop will revert the behavior to treating errors as terminal events. This can be used to accurately scope continue strategy and avoid surprises, specially when combining it with
Typical Reactor operators will throw errors if you:
- Throw any
RuntimeExceptioninside a lambda within an operator (see 4.6.2 for an in-depth explanation)
- Transform a signal into an error one
- Receive an HTTP error code (400s or 500s)
null(except some documented cases)
- Overflow due to not generating enough demand
Until now, we have seen examples that deal with error handling on particular sequences, and while you should continue to use these patterns for most use cases, you might find yourself applying the same operator to a lot of requests. For those cases, Discord4J provides a way to install an error handler across many or all requests made by a
When you build a Discord4J client through
DiscordClientBuilder you'll notice that there are many setters for a variety of customization. You can handle errors in multiple requests by providing a custom
You could, for example, build your clients this way:
onClientResponse is called, you're adding a strategy to transform each response made by the
DiscordClient. If an error occurs, Discord4J processes the error through the following handlers:
- Handle rate limiting errors (429), these cannot be modified.
- Handle the errors using the ones installed by
- Handle server errors (500s) and retry them using exponential backoff.
The first handler that matches will consume the error and apply its strategy, meaning that the order of declaration is important.
You can look at the
ResponseFunction class for commonly used error handlers. A version covering all requests is available, but also a version allowing you to apply the handler to only some API Routes, with the support of
RouteMatcher. Explore the Javadocs for the rest module to understand more.