Skip to main content
Paper provides multiple ways to create commands. The modern approach uses the BasicCommand interface and programmatic registration, while legacy plugins can still use the Command class.
Paper plugins using paper-plugin.yml cannot declare commands in YAML. You must register commands programmatically using JavaPlugin.registerCommand().
The BasicCommand interface provides a simple, Bukkit-style approach to creating commands.

Creating a BasicCommand

From io.papermc.paper.command.brigadier.BasicCommand:
@FunctionalInterface
public interface BasicCommand {
    void execute(CommandSourceStack commandSourceStack, String[] args);
    
    default Collection<String> suggest(CommandSourceStack commandSourceStack, String[] args) {
        return Collections.emptyList();
    }
    
    default boolean canUse(CommandSender sender) {
        String permission = this.permission();
        return permission == null || sender.hasPermission(permission);
    }
    
    default @Nullable String permission() {
        return null;
    }
}
1

Implement BasicCommand

Create a class implementing BasicCommand:
package com.example.myplugin;

import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;

import java.util.Collection;
import java.util.List;

public class HelloCommand implements BasicCommand {

    @Override
    public void execute(@NotNull CommandSourceStack stack, @NotNull String[] args) {
        if (args.length == 0) {
            stack.getSender().sendMessage("Hello, world!");
        } else {
            String name = args[0];
            stack.getSender().sendMessage("Hello, " + name + "!");
        }
    }

    @Override
    public Collection<String> suggest(@NotNull CommandSourceStack stack, @NotNull String[] args) {
        if (args.length == 1) {
            // Suggest online player names
            return stack.getSender().getServer().getOnlinePlayers().stream()
                .map(Player::getName)
                .toList();
        }
        return List.of();
    }

    @Override
    public String permission() {
        return "myplugin.hello";
    }
}
2

Register the Command

Register your command in the plugin’s onEnable() method:
@Override
public void onEnable() {
    // Simple registration
    registerCommand("hello", new HelloCommand());
    
    // With description
    registerCommand("hello", "Say hello to someone", new HelloCommand());
    
    // With description and aliases
    registerCommand("hello", "Say hello to someone", 
        List.of("hi", "greet"), new HelloCommand());
}
From JavaPlugin.java:374-436, the registerCommand() methods:
public void registerCommand(String label, BasicCommand basicCommand)
public void registerCommand(String label, @Nullable String description, BasicCommand basicCommand)
public void registerCommand(String label, Collection<String> aliases, BasicCommand basicCommand)
public void registerCommand(String label, @Nullable String description, 
                           Collection<String> aliases, BasicCommand basicCommand)

CommandSourceStack

The CommandSourceStack (package: io.papermc.paper.command.brigadier.CommandSourceStack) provides context for command execution:
public interface CommandSourceStack {
    Location getLocation();           // Where the command was executed
    CommandSender getSender();        // Who executed the command
    @Nullable Entity getExecutor();   // The executing entity (may differ from sender)
    
    CommandSourceStack withLocation(Location location);
    CommandSourceStack withExecutor(Entity executor);
}
Usage example:
@Override
public void execute(@NotNull CommandSourceStack stack, @NotNull String[] args) {
    CommandSender sender = stack.getSender();
    Location location = stack.getLocation();
    Entity executor = stack.getExecutor();
    
    if (executor instanceof Player player) {
        player.sendMessage("You are at: " + location);
    } else {
        sender.sendMessage("Console commands not supported");
    }
}

Command Class (Legacy)

For advanced use cases, you can extend the Command class directly.

Creating a Command

From org.bukkit.command.Command:
public abstract class Command {
    protected Command(@NotNull String name)
    protected Command(@NotNull String name, @NotNull String description, 
                     @NotNull String usageMessage, @NotNull List<String> aliases)
    
    public abstract boolean execute(@NotNull CommandSender sender, 
                                    @NotNull String commandLabel, 
                                    @NotNull String[] args);
    
    public List<String> tabComplete(@NotNull CommandSender sender, 
                                    @NotNull String alias, 
                                    @NotNull String[] args)
}
Example:
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;

public class TeleportCommand extends Command {

    public TeleportCommand() {
        super("teleport", "Teleport to coordinates", "/teleport <x> <y> <z>", List.of("tp"));
        setPermission("myplugin.teleport");
    }

    @Override
    public boolean execute(@NotNull CommandSender sender, 
                          @NotNull String label, 
                          @NotNull String[] args) {
        if (!(sender instanceof Player player)) {
            sender.sendMessage("Only players can use this command");
            return true;
        }

        if (args.length != 3) {
            return false;  // Shows usage message
        }

        try {
            double x = Double.parseDouble(args[0]);
            double y = Double.parseDouble(args[1]);
            double z = Double.parseDouble(args[2]);
            
            player.teleport(new Location(player.getWorld(), x, y, z));
            player.sendMessage("Teleported!");
            return true;
        } catch (NumberFormatException e) {
            sender.sendMessage("Invalid coordinates");
            return false;
        }
    }

    @Override
    public @NotNull List<String> tabComplete(@NotNull CommandSender sender, 
                                             @NotNull String alias, 
                                             @NotNull String[] args) {
        if (args.length <= 3) {
            return List.of("~");  // Suggest relative coordinates
        }
        return List.of();
    }
}

Tab Completion

Provide suggestions for command arguments:

BasicCommand Tab Completion

public class GamemodeCommand implements BasicCommand {

    @Override
    public void execute(@NotNull CommandSourceStack stack, @NotNull String[] args) {
        if (args.length != 1) {
            stack.getSender().sendMessage("Usage: /gamemode <mode>");
            return;
        }
        
        // Execute command logic
    }

    @Override
    public Collection<String> suggest(@NotNull CommandSourceStack stack, @NotNull String[] args) {
        if (args.length == 1) {
            return List.of("survival", "creative", "adventure", "spectator");
        }
        return List.of();
    }
}

Dynamic Tab Completion

@Override
public Collection<String> suggest(@NotNull CommandSourceStack stack, @NotNull String[] args) {
    if (args.length == 1) {
        String input = args[0].toLowerCase();
        return List.of("survival", "creative", "adventure", "spectator")
            .stream()
            .filter(mode -> mode.startsWith(input))
            .toList();
    }
    return List.of();
}

Permission Checking

BasicCommand Permissions

public class AdminCommand implements BasicCommand {

    @Override
    public void execute(@NotNull CommandSourceStack stack, @NotNull String[] args) {
        // Only executes if player has permission
        stack.getSender().sendMessage("Admin action performed!");
    }

    @Override
    public String permission() {
        return "myplugin.admin";  // Required permission
    }

    @Override
    public boolean canUse(CommandSender sender) {
        // Custom permission logic
        return sender.hasPermission("myplugin.admin") || sender.isOp();
    }
}

Command Class Permissions

From org.bukkit.command.Command:175-225:
command.setPermission("myplugin.teleport");

if (command.testPermission(sender)) {
    // Sender has permission
}

// Test silently (no message sent)
if (command.testPermissionSilent(sender)) {
    // Has permission
}

Command Aliases

Register multiple aliases for a command:
@Override
public void onEnable() {
    registerCommand("teleport", "Teleport to coordinates",
        List.of("tp", "tele"),  // Aliases
        new TeleportCommand());
}
Aliases will not override already existing commands (except namespaced ones). The main command label will override existing commands.

Subcommands

Implement subcommands by parsing the first argument:
public class AdminCommand implements BasicCommand {

    @Override
    public void execute(@NotNull CommandSourceStack stack, @NotNull String[] args) {
        if (args.length == 0) {
            stack.getSender().sendMessage("Usage: /admin <reload|info|debug>");
            return;
        }

        switch (args[0].toLowerCase()) {
            case "reload" -> handleReload(stack);
            case "info" -> handleInfo(stack);
            case "debug" -> handleDebug(stack, args);
            default -> stack.getSender().sendMessage("Unknown subcommand");
        }
    }

    @Override
    public Collection<String> suggest(@NotNull CommandSourceStack stack, @NotNull String[] args) {
        if (args.length == 1) {
            return List.of("reload", "info", "debug");
        }
        return List.of();
    }

    private void handleReload(CommandSourceStack stack) {
        stack.getSender().sendMessage("Reloading...");
    }

    private void handleInfo(CommandSourceStack stack) {
        stack.getSender().sendMessage("Plugin info...");
    }

    private void handleDebug(CommandSourceStack stack, String[] args) {
        stack.getSender().sendMessage("Debug mode toggled");
    }
}

Lifecycle Event Registration

Alternatively, register commands via lifecycle events:
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;

@Override
public void onEnable() {
    getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {
        event.registrar().register(
            "mycommand",
            "Command description",
            List.of("alias1", "alias2"),
            new MyCommand()
        );
    });
}

Best Practices

  1. Validate input:
    if (args.length < 1) {
        sender.sendMessage("Usage: /command <arg>");
        return;
    }
    
  2. Check sender type:
    if (!(sender instanceof Player player)) {
        sender.sendMessage("Only players can use this command");
        return;
    }
    
  3. Use permissions:
    @Override
    public String permission() {
        return "myplugin.command";
    }
    
  4. Provide tab completion:
    @Override
    public Collection<String> suggest(CommandSourceStack stack, String[] args) {
        // Return relevant suggestions
    }
    
  5. Handle errors gracefully:
    try {
        // Command logic
    } catch (Exception e) {
        sender.sendMessage("An error occurred");
        getLogger().severe("Command error: " + e.getMessage());
    }
    
Do not call getCommand() in onEnable() for Paper plugins. Use registerCommand() instead.

Build docs developers (and LLMs) love