Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/discord-jda/JDA/llms.txt

Use this file to discover all available pages before exploring further.

Interactions provide rich, interactive experiences in Discord. This guide covers buttons, select menus, modals, and the new Components V2 system.

Overview

JDA supports several interaction types:
  • Buttons - Clickable buttons in messages
  • Select Menus - Dropdown menus for user selection
  • Modals - Text input forms
  • Components V2 - Advanced UI components (containers, sections, media galleries)

Prerequisites

Interactions don’t require specific gateway intents, making them ideal for lightweight bots.
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.requests.GatewayIntent;
import java.util.EnumSet;

EnumSet<GatewayIntent> intents = EnumSet.noneOf(GatewayIntent.class);
JDA jda = JDABuilder.createLight(token, intents)
    .addEventListeners(new InteractionHandler())
    .build();

Buttons

Creating Buttons

import net.dv8tion.jda.api.components.buttons.Button;
import net.dv8tion.jda.api.components.actionrow.ActionRow;

// Different button styles
Button primary = Button.primary("id:primary", "Primary");
Button secondary = Button.secondary("id:secondary", "Secondary");
Button success = Button.success("id:success", "Success");
Button danger = Button.danger("id:danger", "Danger");
Button link = Button.link("https://discord.com", "Visit Discord");

// Send buttons in a message
channel.sendMessage("Choose an option:")
    .addComponents(
        ActionRow.of(primary, secondary, success, danger),
        ActionRow.of(link)
    )
    .queue();

Button Properties

import net.dv8tion.jda.api.entities.emoji.Emoji;

// Button with emoji
Button withEmoji = Button.primary("like", "Like")
    .withEmoji(Emoji.fromUnicode("U+1F44D"));

// Disabled button
Button disabled = Button.primary("disabled", "Disabled")
    .asDisabled();

// Button with just emoji, no label
Button emojiOnly = Button.secondary("settings", Emoji.fromUnicode("⚙️"));

Handling Button Clicks

import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;

public class ButtonHandler extends ListenerAdapter {
    @Override
    public void onButtonInteraction(ButtonInteractionEvent event) {
        String buttonId = event.getComponentId();
        
        switch (buttonId) {
            case "confirm":
                event.reply("Confirmed!").setEphemeral(true).queue();
                break;
            
            case "cancel":
                // Edit the original message to remove buttons
                event.editMessage("Cancelled.")
                    .setComponents()  // Removes all components
                    .queue();
                break;
            
            case "delete":
                // Delete the original message
                event.deferEdit().queue();
                event.getHook().deleteOriginal().queue();
                break;
        }
    }
}

User-Specific Buttons

Encode user IDs in button IDs to restrict who can click:
// When creating the button
String userId = event.getUser().getId();
Button userButton = Button.danger(userId + ":delete", "Delete");

event.reply("Are you sure?")
    .addComponents(ActionRow.of(userButton))
    .queue();

// When handling the click
@Override
public void onButtonInteraction(ButtonInteractionEvent event) {
    String[] parts = event.getComponentId().split(":");
    String authorId = parts[0];
    String action = parts[1];
    
    // Ignore if wrong user clicked
    if (!authorId.equals(event.getUser().getId())) {
        return;
    }
    
    // Handle the action
    if (action.equals("delete")) {
        event.deferEdit().queue();
        event.getHook().deleteOriginal().queue();
    }
}

Select Menus

String Select Menus

import net.dv8tion.jda.api.components.selections.StringSelectMenu;
import net.dv8tion.jda.api.components.selections.SelectOption;

StringSelectMenu menu = StringSelectMenu.create("menu:role")
    .setPlaceholder("Choose your role")
    .addOption("Tank", "role:tank", "Absorb damage for the team")
    .addOption("Healer", "role:healer", "Keep teammates alive")
    .addOption("DPS", "role:dps", "Deal damage to enemies")
    .setMinValues(1)  // Minimum selections required
    .setMaxValues(1)  // Maximum selections allowed
    .build();

channel.sendMessage("Select your role:")
    .addComponents(ActionRow.of(menu))
    .queue();

Select Menu with Default Values

StringSelectMenu menu = StringSelectMenu.create("settings")
    .addOption("Notifications", "notif", "Enable notifications")
    .addOption("Privacy", "privacy", "Privacy settings")
    .addOption("Display", "display", "Display preferences")
    .setDefaultValues("notif", "privacy")  // Pre-select options
    .setMaxValues(3)
    .build();

Handling Select Menu Events

import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent;

@Override
public void onStringSelectInteraction(StringSelectInteractionEvent event) {
    String menuId = event.getComponentId();
    
    if (menuId.equals("menu:role")) {
        // Get selected values
        List<String> values = event.getValues();
        String role = values.get(0);
        
        event.reply("You selected: " + role)
            .setEphemeral(true)
            .queue();
    }
}

Entity Select Menus

Select Discord entities like users, roles, or channels:
import net.dv8tion.jda.api.components.selections.EntitySelectMenu;
import net.dv8tion.jda.api.components.selections.EntitySelectMenu.SelectTarget;

// User select menu
EntitySelectMenu userMenu = EntitySelectMenu.create("select:user", SelectTarget.USER)
    .setPlaceholder("Select a user")
    .setMinValues(1)
    .setMaxValues(5)
    .build();

// Role select menu
EntitySelectMenu roleMenu = EntitySelectMenu.create("select:role", SelectTarget.ROLE)
    .setPlaceholder("Select roles")
    .build();

// Channel select menu
EntitySelectMenu channelMenu = EntitySelectMenu.create("select:channel", SelectTarget.CHANNEL)
    .setPlaceholder("Select a channel")
    .build();

Modals (Text Input Forms)

Creating and Sending Modals

import net.dv8tion.jda.api.components.text.TextInput;
import net.dv8tion.jda.api.components.text.TextInputStyle;
import net.dv8tion.jda.api.interactions.modals.Modal;

@Override
public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
    if (event.getName().equals("feedback")) {
        TextInput subject = TextInput.create("subject", "Subject", TextInputStyle.SHORT)
            .setPlaceholder("Brief summary")
            .setMinLength(5)
            .setMaxLength(100)
            .setRequired(true)
            .build();
        
        TextInput body = TextInput.create("body", "Feedback", TextInputStyle.PARAGRAPH)
            .setPlaceholder("Your detailed feedback")
            .setMinLength(10)
            .setMaxLength(1000)
            .setRequired(true)
            .build();
        
        Modal modal = Modal.create("modal:feedback", "Provide Feedback")
            .addActionRow(subject)
            .addActionRow(body)
            .build();
        
        event.replyModal(modal).queue();
    }
}

Handling Modal Submissions

import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;

@Override
public void onModalInteraction(ModalInteractionEvent event) {
    if (event.getModalId().equals("modal:feedback")) {
        String subject = event.getValue("subject").getAsString();
        String body = event.getValue("body").getAsString();
        
        // Process the feedback
        processFeedback(subject, body);
        
        event.reply("Thank you for your feedback!")
            .setEphemeral(true)
            .queue();
    }
}

Components V2

Components V2 requires calling .useComponentsV2() on your message reply/edit.

Containers and Sections

Create rich, structured layouts:
import net.dv8tion.jda.api.components.container.Container;
import net.dv8tion.jda.api.components.section.Section;
import net.dv8tion.jda.api.components.textdisplay.TextDisplay;
import net.dv8tion.jda.api.components.thumbnail.Thumbnail;
import net.dv8tion.jda.api.components.separator.Separator;
import net.dv8tion.jda.api.utils.FileUpload;

Container container = Container.of(
    // Section with thumbnail and text
    Section.of(
        Thumbnail.fromFile(FileUpload.fromData(imageBytes, "image.png")),
        TextDisplay.of("## Welcome to the Server"),
        TextDisplay.of("Here's some important information"),
        TextDisplay.of("-# Small footnote text")
    ),
    
    // Separator
    Separator.createDivider(Separator.Spacing.SMALL),
    
    // Section with button
    Section.of(
        Button.primary("rules", "Read Rules"),
        TextDisplay.of("**Rules:** Be respectful to others"),
        TextDisplay.of("**Status:** Active")
    )
);

event.replyComponents(container)
    .useComponentsV2()  // Required!
    .setEphemeral(true)
    .queue();

Media Galleries

import net.dv8tion.jda.api.components.mediagallery.MediaGallery;
import net.dv8tion.jda.api.components.mediagallery.MediaGalleryItem;

MediaGallery gallery = MediaGallery.of(
    MediaGalleryItem.fromFile(FileUpload.fromData(image1, "photo1.jpg")),
    MediaGalleryItem.fromFile(FileUpload.fromData(image2, "photo2.jpg")),
    MediaGalleryItem.fromFile(FileUpload.fromData(image3, "photo3.jpg"))
);

Container container = Container.of(
    TextDisplay.of("Check out these images:"),
    gallery
);

event.replyComponents(container)
    .useComponentsV2()
    .queue();

File Displays

import net.dv8tion.jda.api.components.filedisplay.FileDisplay;

FileDisplay file = FileDisplay.fromFile(
    FileUpload.fromData("{}".getBytes(), "config.json")
);

Container container = Container.of(
    TextDisplay.of("Download your configuration:"),
    file
);

Component Replacement

Use unique IDs to replace specific components:
import net.dv8tion.jda.api.components.replacer.ComponentReplacer;

@Override
public void onButtonInteraction(ButtonInteractionEvent event) {
    // Disable the clicked button
    MessageComponentTree updated = event.getMessage()
        .getComponentTree()
        .replace(ComponentReplacer.byId(
            event.getButton(),
            event.getButton().asDisabled()
        ));
    
    event.editComponents(updated).queue();
}

// Disable all components
MessageComponentTree allDisabled = event.getMessage()
    .getComponentTree()
    .asDisabled();

event.editComponents(allDisabled).queue();

Interaction Responses

Response Types

// Immediate reply (public)
event.reply("Response").queue();

// Ephemeral reply (only user sees it)
event.reply("Secret message").setEphemeral(true).queue();

// Deferred reply (shows "thinking" state)
event.deferReply().queue();
event.getHook().sendMessage("Done!").queue();

// Edit the original message
event.editMessage("Updated").queue();

// Deferred edit (no "thinking" state)
event.deferEdit().queue();
event.getHook().editOriginal("Updated").queue();

Following Up

@Override
public void onButtonInteraction(ButtonInteractionEvent event) {
    // Reply to the interaction
    event.reply("Processing...").setEphemeral(true).queue();
    
    // Send follow-up messages
    event.getHook()
        .sendMessage("Step 1 complete")
        .queue();
    
    event.getHook()
        .sendMessage("Step 2 complete")
        .queue();
}

Best Practices

Component Design:
  • Use descriptive labels and placeholders
  • Limit action rows to 5 per message
  • Each action row can have up to 5 buttons or 1 select menu
  • Disable or remove components after they’re used
Important Constraints:
  • You must respond to interactions within 3 seconds
  • Component custom IDs are limited to 100 characters
  • Use deferReply() or deferEdit() for long operations
  • Components V2 requires .useComponentsV2() on the message

Practical Examples

Confirmation Dialog

public void showConfirmation(SlashCommandInteractionEvent event) {
    String userId = event.getUser().getId();
    
    event.reply("Are you sure you want to proceed?")
        .addComponents(
            ActionRow.of(
                Button.success(userId + ":confirm", "Yes"),
                Button.danger(userId + ":cancel", "No")
            )
        )
        .setEphemeral(true)
        .queue();
}

@Override
public void onButtonInteraction(ButtonInteractionEvent event) {
    String[] parts = event.getComponentId().split(":");
    if (!parts[0].equals(event.getUser().getId())) return;
    
    if (parts[1].equals("confirm")) {
        event.editMessage("Confirmed! Processing...")
            .setComponents()
            .queue();
        // Do the action
    } else {
        event.editMessage("Cancelled.")
            .setComponents()
            .queue();
    }
}

Pagination

private int currentPage = 0;
private final int totalPages = 5;

public void showPage(ButtonInteractionEvent event) {
    event.editMessage("Page " + (currentPage + 1) + " of " + totalPages)
        .setComponents(
            ActionRow.of(
                Button.primary("prev", "Previous")
                    .asDisabled(currentPage == 0),
                Button.primary("next", "Next")
                    .asDisabled(currentPage == totalPages - 1)
            )
        )
        .queue();
}

@Override
public void onButtonInteraction(ButtonInteractionEvent event) {
    switch (event.getComponentId()) {
        case "prev" -> currentPage--;
        case "next" -> currentPage++;
    }
    showPage(event);
}

Next Steps

Build docs developers (and LLMs) love