A RestAction is JDA’s way of handling asynchronous API requests. It provides a terminal interface between the user and Discord’s API, allowing you to control how and when requests are executed.
Overview
RestAction provides three main execution methods:
- queue() - Asynchronous execution with callbacks (recommended)
- submit() - Returns a CompletableFuture for the result
- complete() - Blocks the current thread until completion
Execution Methods
queue
Submits the request for asynchronous execution.
Callback executed on success
failure
Consumer<? super Throwable>
Callback executed on failure
Variants:
queue() - No callbacks, uses defaults
queue(Consumer<T> success) - Success callback only
queue(Consumer<T> success, Consumer<Throwable> failure) - Both callbacks
// Simple queue with no callback
channel.sendMessage("Hello!").queue();
// With success callback
channel.sendMessage("Hello!").queue(message -> {
System.out.println("Message sent: " + message.getId());
});
// With both callbacks
user.openPrivateChannel().queue(
channel -> channel.sendMessage("DM sent!").queue(),
error -> System.err.println("Failed to open DM: " + error.getMessage())
);
submit
Submits the request and returns a CompletableFuture.
Whether to automatically handle rate limits
Future that completes with the response value
CompletableFuture<Message> future = channel.sendMessage("Hello!").submit();
future.thenAccept(message -> {
System.out.println("Message sent: " + message.getId());
}).exceptionally(error -> {
error.printStackTrace();
return null;
});
// Chain multiple requests
jda.retrieveUserById(userId).submit()
.thenCompose(user -> user.openPrivateChannel().submit())
.thenCompose(channel -> channel.sendMessage("Hello!").submit())
.whenComplete((msg, error) -> {
if (error != null) error.printStackTrace();
});
complete
Blocks the current thread until the request completes.
Whether to automatically handle rate limits
Not recommended for most use cases. Blocking threads can cause performance issues. Use queue() or submit() instead.
// Block until message is sent
Message message = channel.sendMessage("Hello!").complete();
System.out.println("Sent: " + message.getId());
// This blocks the thread!
User user = jda.retrieveUserById(userId).complete();
map
Transforms the result to a different type.
map
Function<? super T, ? extends O>
required
Mapping function to apply
RestAction with the mapped type
// Get user's name from ID
RestAction<String> nameAction = jda.retrieveUserById(userId)
.map(User::getName);
nameAction.queue(name -> {
System.out.println("User name: " + name);
});
// Get message content length
channel.sendMessage("Hello")
.map(Message::getContentRaw)
.map(String::length)
.queue(length -> System.out.println("Length: " + length));
flatMap
Chains another RestAction based on the result.
flatMap
Function<? super T, ? extends RestAction<O>>
required
Function that returns the next RestAction
RestAction for the chained operation
// Send a DM to a user
jda.retrieveUserById(userId)
.flatMap(User::openPrivateChannel)
.flatMap(channel -> channel.sendMessage("Hello!"))
.queue(message -> System.out.println("DM sent!"));
// Conditional flatMap
channel.retrieveMessageById(messageId)
.flatMap(
msg -> msg.getAuthor().isBot(), // condition
msg -> msg.delete() // only if condition is true
)
.queue();
onSuccess
Executes a consumer on success and continues the chain.
consumer
Consumer<? super T>
required
Consumer to execute on success
RestAction that consumes but preserves the result
channel.sendMessage("Hello!")
.onSuccess(msg -> System.out.println("Sent: " + msg.getId()))
.flatMap(msg -> msg.addReaction("✅"))
.queue();
Error Handling
onErrorMap
Provides a fallback value on failure.
condition
Predicate<? super Throwable>
Optional condition to check before applying fallback
map
Function<? super Throwable, ? extends T>
required
Function to provide fallback value
RestAction with fallback handling
import static net.dv8tion.jda.api.requests.ErrorResponse.*;
// Provide default value on any error
jda.retrieveUserById(userId)
.map(User::getName)
.onErrorMap(error -> "Unknown User")
.queue(name -> System.out.println("Name: " + name));
// Conditional fallback
user.openPrivateChannel()
.flatMap(channel -> channel.sendMessage("Hello"))
.onErrorMap(
CANNOT_SEND_TO_USER::test,
error -> null
)
.queue();
onErrorFlatMap
Provides a fallback RestAction on failure.
condition
Predicate<? super Throwable>
Optional condition to check before applying fallback
map
Function<? super Throwable, ? extends RestAction<? extends T>>
required
Function to provide fallback action
RestAction with fallback action
import static net.dv8tion.jda.api.requests.ErrorResponse.*;
// Try DM, fallback to channel message
user.openPrivateChannel()
.flatMap(channel -> channel.sendMessage("Hello!"))
.onErrorFlatMap(
CANNOT_SEND_TO_USER::test,
error -> textChannel.sendMessage(user.getAsMention() + " Hello!")
)
.queue();
mapToResult
Wraps success and failure in a Result object.
RestAction that never fails, wrapping errors in Result
RestAction<Result<User>> result = jda.retrieveUserById(userId)
.mapToResult();
result.queue(r -> {
if (r.isSuccess()) {
System.out.println("User: " + r.get().getName());
} else {
System.out.println("Failed: " + r.getFailure());
}
});
Combining Actions
and
Combines two RestActions and computes a result from both.
The other action to combine
accumulator
BiFunction<? super T, ? super U, ? extends O>
Function to combine results (optional)
RestAction<User> userAction = jda.retrieveUserById(userId);
RestAction<Guild> guildAction = jda.retrieveGuildById(guildId);
// Combine both results
userAction.and(guildAction, (user, guild) ->
user.getName() + " in " + guild.getName()
).queue(result -> System.out.println(result));
// Just wait for both to complete
userAction.and(guildAction).queue();
allOf
Accumulates multiple RestActions into a list.
first
RestAction<? extends E>
required
First action
others
RestAction<? extends E>...
Additional actions
RestAction containing list of all results
RestAction<List<User>> allUsers = RestAction.allOf(
jda.retrieveUserById(id1),
jda.retrieveUserById(id2),
jda.retrieveUserById(id3)
);
allUsers.queue(users -> {
users.forEach(user -> System.out.println(user.getName()));
});
zip
Combines this action with others into a list.
first
RestAction<? extends T>
required
First action to zip
other
RestAction<? extends T>...
Additional actions to zip
RestAction containing list of all results
RestAction<Message> msg1 = channel.sendMessage("First");
RestAction<List<Message>> all = msg1.zip(
channel.sendMessage("Second"),
channel.sendMessage("Third")
);
all.queue(messages -> {
System.out.println("Sent " + messages.size() + " messages");
});
Delay Operations
delay
Delays the result by a specified duration.
Time unit for numeric delay
Custom scheduler (null for default)
// Self-destructing message
channel.sendMessage("This will be deleted in 5 seconds")
.delay(Duration.ofSeconds(5))
.flatMap(Message::delete)
.queue();
// Chain delays
channel.sendMessage("Step 1")
.delay(2, TimeUnit.SECONDS)
.flatMap(msg -> msg.editMessage("Step 2"))
.delay(2, TimeUnit.SECONDS)
.flatMap(msg -> msg.editMessage("Step 3"))
.queue();
queueAfter
Schedules the action to execute after a delay.
failure
Consumer<? super Throwable>
Failure callback
Future for the scheduled operation
// Delete after 10 seconds
message.delete().queueAfter(10, TimeUnit.SECONDS);
// With callback
channel.sendMessage("Delayed").queueAfter(
5,
TimeUnit.SECONDS,
msg -> System.out.println("Sent after delay")
);
submitAfter
Schedules the action and returns a CompletableFuture.
return
DelayedCompletableFuture<T>
Future for the delayed operation
DelayedCompletableFuture<Message> future =
channel.sendMessage("Delayed").submitAfter(5, TimeUnit.SECONDS);
future.thenAccept(msg -> System.out.println("Sent!"));
completeAfter
Blocks for the delay then executes the action.
Blocks the current thread. Not recommended for most use cases.
Checks and Timeouts
setCheck
Sets a last-second check before execution.
Checks to run before executing (null = no checks)
Message toDelete = /* ... */;
toDelete.delete()
.setCheck(() -> !toDelete.isPinned()) // Only delete if not pinned
.queue();
addCheck
Adds additional checks to existing checks.
RestAction with added checks
action.setCheck(() -> condition1())
.addCheck(() -> condition2())
.queue();
timeout
Sets a timeout for the action.
jda.retrieveUserById(userId)
.timeout(5, TimeUnit.SECONDS)
.queue(
user -> System.out.println("Found: " + user.getName()),
error -> System.err.println("Timed out or failed")
);
deadline
Sets an absolute deadline timestamp.
Millisecond timestamp for deadline
long deadline = System.currentTimeMillis() + 10000; // 10 seconds from now
action.deadline(deadline)
.queueAfter(20, TimeUnit.SECONDS); // Will timeout after 10 seconds
Static Configuration
setDefaultFailure
Sets the default failure callback for all RestActions.
callback
Consumer<? super Throwable>
The default failure handler (null to ignore)
RestAction.setDefaultFailure(error -> {
System.err.println("Request failed: " + error.getMessage());
});
setDefaultSuccess
Sets the default success callback for all RestActions.
The default success handler (null to ignore)
RestAction.setDefaultSuccess(result -> {
System.out.println("Request completed");
});
setDefaultTimeout
Sets the default timeout for all RestActions.
Timeout value (0 = no timeout)
RestAction.setDefaultTimeout(30, TimeUnit.SECONDS);
setPassContext
Enables context tracking for better error messages.
May impact performance. Creates stack traces for every execution.
RestAction.setPassContext(true);
Best Practices
Use queue() for Async Operations
// Good - non-blocking
channel.sendMessage("Hello").queue();
// Bad - blocks the thread
channel.sendMessage("Hello").complete();
Chain Operations with flatMap
// Good - clean chain
jda.retrieveUserById(userId)
.flatMap(User::openPrivateChannel)
.flatMap(channel -> channel.sendMessage("Hello"))
.queue();
// Bad - nested callbacks
jda.retrieveUserById(userId).queue(user -> {
user.openPrivateChannel().queue(channel -> {
channel.sendMessage("Hello").queue();
});
});
Handle Errors Gracefully
user.openPrivateChannel()
.flatMap(channel -> channel.sendMessage("Hello"))
.queue(
message -> System.out.println("DM sent"),
error -> System.err.println("Failed to send DM: " + error.getMessage())
);