Skip to content

Exceptions

If a command cannot run with the input it was given, you can throw a CommandSyntaxException. The server catches it and sends its message to the command sender in red, without logging a stack trace. You can throw it in the executes(...) method of a command node, as well as in the parse and convert methods of custom arguments.

Instead of constructing a CommandSyntaxException yourself, you create an exception type, which holds the message, and call its create(...) method whenever you want to throw the exception. Brigadier provides the following exception types, all of which implement the CommandExceptionType interface:

ClassMessageCreating an exception
SimpleCommandExceptionTypeA fixed Message.create()
DynamicCommandExceptionTypeA Function<Object, Message>.create(Object)
Dynamic2CommandExceptionType to Dynamic4CommandExceptionTypeA function taking two to four objects.create(Object, Object), and so on
DynamicNCommandExceptionTypeA function taking an Object[].createWithContext(ImmutableStringReader, Object...)

An exception type can be used to create any number of exceptions, so declare it as a constant and reuse it:

private static final SimpleCommandExceptionType ERROR_NOT_PLAYER = new SimpleCommandExceptionType(
new LiteralMessage("Only players can use this command!")
);
private static final DynamicCommandExceptionType ERROR_FULL_HEALTH = new DynamicCommandExceptionType(name -> {
return new LiteralMessage(name + " already has full health!");
});

You can then throw them in your command:

Commands.literal("heal")
.executes(ctx -> {
if (!(ctx.getSource().getExecutor() instanceof Player player)) {
throw ERROR_NOT_PLAYER.create();
}
final double maxHealth = player.getAttribute(Attribute.MAX_HEALTH).getValue();
if (player.getHealth() >= maxHealth) {
throw ERROR_FULL_HEALTH.create(player.getName());
}
player.setHealth(maxHealth);
return Command.SINGLE_SUCCESS;
});

LiteralMessage only contains plain text. If you want to use a Component as the message, convert it using the MessageComponentSerializer:

private static final DynamicCommandExceptionType ERROR_FULL_HEALTH = new DynamicCommandExceptionType(name -> {
return MessageComponentSerializer.message().serialize(MiniMessage.miniMessage().deserialize(
"<gold><name></gold> already has full health!",
Placeholder.unparsed("name", name.toString())
));
});

The message is still shown in red, but parts with their own color, like the name above, keep it.

While a custom argument is parsing its input in parse(StringReader), you can use createWithContext(reader, ...) instead of create(...). The exception then remembers the input and the position of the reader’s cursor, and the server shows a second line below the message. This line contains up to 10 characters before the cursor, the rest of the input in red and underlined, and <--[HERE]. Clicking the line puts the command back into the chat box.

The following argument only accepts even numbers. It moves the cursor back to the start of the number before throwing, so that the whole number is marked, which is also what Brigadier’s own argument types do:

EvenIntegerArgument.java
public final class EvenIntegerArgument implements CustomArgumentType<Integer, Integer> {
private static final DynamicCommandExceptionType ERROR_NOT_EVEN = new DynamicCommandExceptionType(value -> {
return new LiteralMessage(value + " is not an even number!");
});
@Override
public Integer parse(final StringReader reader) throws CommandSyntaxException {
final int start = reader.getCursor();
final int value = reader.readInt();
if (value % 2 != 0) {
reader.setCursor(start);
throw ERROR_NOT_EVEN.createWithContext(reader, value);
}
return value;
}
@Override
public ArgumentType<Integer> getNativeType() {
return IntegerArgumentType.integer();
}
}

Running /split 7 with this argument shows the following, with the 7 in the second line marked in red:

7 is not an even number!
split 7<--[HERE]

The second line is only shown for exceptions thrown while the command is being parsed. Exceptions thrown in executes(...) or in CustomArgumentType.Converted#convert only show the message, even if they were created with context.

CommandSyntaxException.BUILT_IN_EXCEPTIONS provides the exception types Brigadier uses itself, for example when a number argument is out of range. On Paper, Vanilla replaces them with translatable messages, so players see them in the language of their client. You can reuse them in your own code instead of writing your own messages:

throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.integerTooHigh().createWithContext(reader, value, maximum);

Note that the exceptions for numbers that are too low or too high take the found value first and the limit second. The exception above shows Integer must not be more than <maximum>: found <value>.

The following table lists all built-in exceptions with their English message:

MethodArgumentsMessage
doubleTooLow()found, minimumDouble must not be less than minimum: found found
doubleTooHigh()found, maximumDouble must not be more than maximum: found found
floatTooLow()found, minimumFloat must not be less than minimum: found found
floatTooHigh()found, maximumFloat must not be more than maximum: found found
integerTooLow()found, minimumInteger must not be less than minimum: found found
integerTooHigh()found, maximumInteger must not be more than maximum: found found
longTooLow()found, minimumLong must not be less than minimum: found found
longTooHigh()found, maximumLong must not be more than maximum: found found
literalIncorrect()expectedExpected literal expected
readerExpectedStartOfQuote()Expected quote to start a string
readerExpectedEndOfQuote()Unclosed quoted string
readerInvalidEscape()characterInvalid escape sequence ‘\character’ in quoted string
readerInvalidBool()valueInvalid boolean: expected ‘true’ or ‘false’ but found ‘value’
readerInvalidInt()valueInvalid integer ‘value’
readerExpectedInt()Expected integer
readerInvalidLong()valueInvalid long ‘value’
readerExpectedLong()Expected long
readerInvalidDouble()valueInvalid double ‘value’
readerExpectedDouble()Expected double
readerInvalidFloat()valueInvalid float ‘value’
readerExpectedFloat()Expected float
readerExpectedBool()Expected boolean
readerExpectedSymbol()symbolExpected ‘symbol’
dispatcherUnknownCommand()Unknown or incomplete command. See below for error
dispatcherUnknownArgument()Incorrect argument for command
dispatcherExpectedArgumentSeparator()Expected whitespace to end one argument, but found trailing data
dispatcherParseException()messageCould not parse command: message