Overview
This bot implements four slash commands:/ban- Ban a user with optional reason and message deletion/say- Make the bot repeat a message/leave- Make the bot leave the server/prune- Delete multiple messages with confirmation
Setup
Configure Gateway Intents
Slash commands don’t require any specific intents:
EnumSet<GatewayIntent> intents = EnumSet.noneOf(GatewayIntent.class);
JDA jda = JDABuilder.createLight("BOT_TOKEN_HERE", intents)
.addEventListeners(new SlashBotExample())
.build();
Register Your Commands
Use
updateCommands() to register slash commands with Discord:CommandListUpdateAction commands = jda.updateCommands();
commands.addCommands(
Commands.slash("say", "Makes the bot say what you tell it to")
.addOption(STRING, "content", "What the bot should say", true)
);
commands.queue();
You might need to reload your Discord client to see newly registered commands.
Handle Command Interactions
Override
onSlashCommandInteraction to handle commands:@Override
public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
switch (event.getName()) {
case "say":
String content = event.getOption("content").getAsString();
event.reply(content).queue();
break;
}
}
Creating Commands with Options
Required Options
Options can be required or optional:commands.addCommands(
Commands.slash("ban", "Ban a user from this server")
.addOptions(new OptionData(USER, "user", "The user to ban")
.setRequired(true)) // Must be provided
.addOptions(new OptionData(INTEGER, "del_days", "Delete messages from the past days")
.setRequiredRange(0, 7)) // Optional, but if provided must be 0-7
.addOptions(new OptionData(STRING, "reason", "The ban reason"))
);
Command Contexts and Integration Types
Control where commands can be used:// Available everywhere (Bot DMs, Guilds, Friend DMs, Group DMs)
Commands.slash("say", "Makes the bot say what you tell it to")
.setContexts(InteractionContextType.ALL)
.setIntegrationTypes(IntegrationType.ALL)
.addOption(STRING, "content", "What the bot should say", true);
// Only in guilds
Commands.slash("ban", "Ban a user from this server")
.setContexts(InteractionContextType.GUILD)
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.BAN_MEMBERS));
Permission Requirements
Set default permissions for who can see and use commands:Commands.slash("leave", "Make the bot leave the server")
.setContexts(InteractionContextType.GUILD)
// Only admins can see this command
.setDefaultPermissions(DefaultMemberPermissions.DISABLED);
Commands.slash("prune", "Prune messages from this channel")
.addOption(INTEGER, "amount", "How many messages to prune (Default 100)")
.setContexts(InteractionContextType.GUILD)
// Only members with MESSAGE_MANAGE permission can see this
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MESSAGE_MANAGE));
Handling Command Interactions
Accessing Options
Retrieve option values from the event:@Override
public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
switch (event.getName()) {
case "ban":
// Required option - no null check needed
User user = event.getOption("user").getAsUser();
Member member = event.getOption("user").getAsMember();
// Optional with default value
int delDays = event.getOption("del_days", 0, OptionMapping::getAsInt);
// Optional with lazy fallback
String reason = event.getOption(
"reason",
() -> "Banned by " + event.getUser().getName(),
OptionMapping::getAsString
);
ban(event, user, member);
break;
}
}
Deferring Replies
For operations that take time, defer the reply:public void ban(SlashCommandInteractionEvent event, User user, Member member) {
// Acknowledge the command immediately
event.deferReply(true).queue(); // true = ephemeral
// Get the interaction hook for later responses
InteractionHook hook = event.getHook();
hook.setEphemeral(true); // All messages will be ephemeral
// Perform checks
if (!event.getMember().hasPermission(Permission.BAN_MEMBERS)) {
hook.sendMessage("You do not have permission to ban users.").queue();
return;
}
// Execute the ban
event.getGuild()
.ban(user, delDays, TimeUnit.DAYS)
.reason(reason)
.flatMap(v -> hook.sendMessage("Banned user " + user.getName()))
.queue();
}
Interactive Components with Buttons
Add buttons to your responses for confirmation dialogs:public void prune(SlashCommandInteractionEvent event) {
OptionMapping amountOption = event.getOption("amount");
int amount = amountOption == null ? 100 : (int) Math.min(200, Math.max(2, amountOption.getAsLong()));
String userId = event.getUser().getId();
// Prompt with buttons
event.reply("This will delete " + amount + " messages.\nAre you sure?")
.addComponents(ActionRow.of(
Button.secondary(userId + ":delete", "Nevermind!"),
Button.danger(userId + ":prune:" + amount, "Yes!")
))
.queue();
}
Handling Button Clicks
@Override
public void onButtonInteraction(ButtonInteractionEvent event) {
String[] id = event.getComponentId().split(":");
String authorId = id[0];
String type = id[1];
// Verify the user clicking is the one who ran the command
if (!authorId.equals(event.getUser().getId())) {
return;
}
// Acknowledge the button click
event.deferEdit().queue();
MessageChannel channel = event.getChannel();
switch (type) {
case "prune" -> {
int amount = Integer.parseInt(id[2]);
event.getChannel()
.getIterableHistory()
.skipTo(event.getMessageIdLong())
.takeAsync(amount)
.thenAccept(channel::purgeMessages);
// Delete the prompt message
event.getHook().deleteOriginal().queue();
}
case "delete" -> {
event.getHook().deleteOriginal().queue();
}
}
}
Complete Example
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.components.actionrow.ActionRow;
import net.dv8tion.jda.api.components.buttons.Button;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.interactions.IntegrationType;
import net.dv8tion.jda.api.interactions.InteractionContextType;
import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.requests.restaction.CommandListUpdateAction;
import java.util.EnumSet;
import java.util.concurrent.TimeUnit;
import static net.dv8tion.jda.api.interactions.commands.OptionType.*;
public class SlashBotExample extends ListenerAdapter {
public static void main(String[] args) {
EnumSet<GatewayIntent> intents = EnumSet.noneOf(GatewayIntent.class);
JDA jda = JDABuilder.createLight("BOT_TOKEN_HERE", intents)
.addEventListeners(new SlashBotExample())
.build();
CommandListUpdateAction commands = jda.updateCommands();
commands.addCommands(
Commands.slash("ban", "Ban a user from this server. Requires permission to ban users.")
.addOptions(new OptionData(USER, "user", "The user to ban").setRequired(true))
.addOptions(new OptionData(INTEGER, "del_days", "Delete messages from the past days.").setRequiredRange(0, 7))
.addOptions(new OptionData(STRING, "reason", "The ban reason to use (default: Banned by <user>)"))
.setContexts(InteractionContextType.GUILD)
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.BAN_MEMBERS))
);
commands.addCommands(
Commands.slash("say", "Makes the bot say what you tell it to")
.setContexts(InteractionContextType.ALL)
.setIntegrationTypes(IntegrationType.ALL)
.addOption(STRING, "content", "What the bot should say", true)
);
commands.addCommands(
Commands.slash("leave", "Make the bot leave the server")
.setContexts(InteractionContextType.GUILD)
.setDefaultPermissions(DefaultMemberPermissions.DISABLED)
);
commands.addCommands(
Commands.slash("prune", "Prune messages from this channel")
.addOption(INTEGER, "amount", "How many messages to prune (Default 100)")
.setContexts(InteractionContextType.GUILD)
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MESSAGE_MANAGE))
);
commands.queue();
}
@Override
public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
if (event.getGuild() == null) {
return;
}
switch (event.getName()) {
case "ban":
Member member = event.getOption("user").getAsMember();
User user = event.getOption("user").getAsUser();
ban(event, user, member);
break;
case "say":
say(event, event.getOption("content").getAsString());
break;
case "leave":
leave(event);
break;
case "prune":
prune(event);
break;
default:
event.reply("I can't handle that command right now :(")
.setEphemeral(true)
.queue();
}
}
@Override
public void onButtonInteraction(ButtonInteractionEvent event) {
String[] id = event.getComponentId().split(":");
String authorId = id[0];
String type = id[1];
if (!authorId.equals(event.getUser().getId())) {
return;
}
event.deferEdit().queue();
MessageChannel channel = event.getChannel();
switch (type) {
case "prune" -> {
int amount = Integer.parseInt(id[2]);
event.getChannel()
.getIterableHistory()
.skipTo(event.getMessageIdLong())
.takeAsync(amount)
.thenAccept(channel::purgeMessages);
event.getHook().deleteOriginal().queue();
}
case "delete" -> {
event.getHook().deleteOriginal().queue();
}
}
}
public void ban(SlashCommandInteractionEvent event, User user, Member member) {
event.deferReply(true).queue();
InteractionHook hook = event.getHook();
hook.setEphemeral(true);
if (!event.getMember().hasPermission(Permission.BAN_MEMBERS)) {
hook.sendMessage("You do not have the required permissions to ban users from this server.").queue();
return;
}
Member selfMember = event.getGuild().getSelfMember();
if (!selfMember.hasPermission(Permission.BAN_MEMBERS)) {
hook.sendMessage("I don't have the required permissions to ban users from this server.").queue();
return;
}
if (member != null && !selfMember.canInteract(member)) {
hook.sendMessage("This user is too powerful for me to ban.").queue();
return;
}
int delDays = event.getOption("del_days", 0, OptionMapping::getAsInt);
String reason = event.getOption("reason", () -> "Banned by " + event.getUser().getName(), OptionMapping::getAsString);
event.getGuild()
.ban(user, delDays, TimeUnit.DAYS)
.reason(reason)
.flatMap(v -> hook.sendMessage("Banned user " + user.getName()))
.queue();
}
public void say(SlashCommandInteractionEvent event, String content) {
event.reply(content).queue();
}
public void leave(SlashCommandInteractionEvent event) {
if (!event.getMember().hasPermission(Permission.KICK_MEMBERS)) {
event.reply("You do not have permissions to kick me.")
.setEphemeral(true)
.queue();
} else {
event.reply("Leaving the server... :wave:")
.flatMap(v -> event.getGuild().leave())
.queue();
}
}
public void prune(SlashCommandInteractionEvent event) {
OptionMapping amountOption = event.getOption("amount");
int amount = amountOption == null ? 100 : (int) Math.min(200, Math.max(2, amountOption.getAsLong()));
String userId = event.getUser().getId();
event.reply("This will delete " + amount + " messages.\nAre you sure?")
.addComponents(ActionRow.of(
Button.secondary(userId + ":delete", "Nevermind!"),
Button.danger(userId + ":prune:" + amount, "Yes!")
))
.queue();
}
}
Use ephemeral replies (
setEphemeral(true)) for messages that only the command user should see, like error messages or confirmations.