Brigadier and commands
Verified against Minecraft 26.3 · Part XIII · You type
/give @p diamond_sword[minecraft:damage=5]into the chat box: three parsers see that string, two of them throw their answer away — and not one of the completions you accepted along the way left your machine.
Open the chat box and type a slash. Before you have finished the word, the text is coloured, a grey hint has appeared behind the cursor and a completion popup is open — and every bit of that was produced by a real Brigadier dispatcher running on your own machine, with real parsers built against your own registries, from a tree the server sent you when you joined. The client is not pattern-matching strings. It parses the whole line on every keystroke, throws the parse away, and sends the string.
Which raises the question this page exists to answer: if the client can parse the command, why is completing an item id instant and completing a loot table not? Because a loot table is data the client was never sent. The tree the client rebuilt says, node by node, which nodes ask the server, and an argument over a registry the client lacks asks it too. Vanilla’s command code declares 454 argument nodes, and only 66 of them serialise as ask the server — so the local path is the rule, not the exception. What makes the round trip feel ubiquitous is which completions take it: they are mostly the ones over data the client was never sent.
The cast
| class | what it decides | side |
|---|---|---|
Commands | the one server-side CommandDispatcher, every registration, and what a failed parse is called | server |
CommandSourceStack | who is running this, from where — position, rotation, level, entity, permissions, output sink. Immutable; a change returns a copy | server |
CommandBuildContext | the registries an argument type parses against, so a data pack’s biome is completable with no code change | both |
ArgumentTypeInfos | the wire description of an argument type: a ArgumentTypeInfo.Template that can be written to a buffer and instantiated on the far side | both |
SuggestionProviders | the four providers that have a name on the wire — ask_server, available_sounds, summonable_entities and post_effects. Everything else serialises as ask_server too | both |
SharedSuggestionProvider | the interface both sides’ sources implement, so one argument type can complete on either. It extends PermissionSetSupplier, and its static SharedSuggestionProvider.suggest family is where most completion happens | both |
ClientSuggestionProvider | the client’s implementation of it: the tab list, the looked-at block and entity, and the one method that sends a packet | client |
CommandSuggestions | the widget over it — the highlighter, the usage hint, the popup and the parse cache | client |
BrigadierExceptions | installed once into Brigadier’s global exception provider, which is why a parse error is a translatable Component | both |
Brigadier itself — the dispatcher, the tree of literal and argument nodes, each with a requirement predicate and optionally executable — is Mojang’s parsing library and lives outside the game’s packages. Everything on this page is what Minecraft builds on top of it.
Three parsers see one string
sequenceDiagram
box Client
participant CSug as Command<br/>Suggestions
participant CPL as ClientPacketListener
participant CSP as ClientSuggestion<br/>Provider
end
box Server
participant SGPL as ServerGamePacket<br/>ListenerImpl
participant Cmds as Commands
participant GC as GiveCommand
end
CSug->>CPL: getCommands — the client's own dispatcher
CSug->>CSug: updateCommandInfo — parse 1, every keystroke
opt a node, or a registry the client lacks, asks the server
CSug->>CSP: customSuggestion
CSP->>SGPL: ServerboundCommand<br/>SuggestionPacket
SGPL-->>CPL: ClientboundCommand<br/>SuggestionsPacket
CPL->>CSP: completeCustomSuggestions — a stale id is dropped
end
CPL->>CPL: sendCommand, on Enter — parse 2, for signatures only
CPL->>SGPL: ServerboundChatCommandPacket — the string
SGPL->>SGPL: tryHandleChat — illegal characters, on the Netty thread
rect rgba(0, 0, 0, 0.04)
Note over SGPL,GC: the server thread
SGPL->>SGPL: parseCommand — parse 3, the one that is used
SGPL->>Cmds: performCommand
Cmds->>GC: the registered lambda
GC->>GC: giveItem — Inventory.add, then sendSuccess
end
Three parses of one /give — two on the client whose answers are thrown away, and the server’s, the only one that runs; the round trip in the frame is taken only when a node asks the server or an argument needs data the client lacks, and /give has neither.
The three parsers of the title are in there: the client’s, running on every keystroke; the client’s again, once, when you press Enter; and the server’s, which is the only one whose answer is used. Each arrow below is a decision.
The client parses first, and the parse never leaves the machine.
CommandSuggestions.updateCommandInfo runs the whole string through the
client’s dispatcher on every keystroke — the one
ClientPacketListener.getCommands hands it, rebuilt from the tree the server
sent — and that parse produces the red tail, Brigadier’s smart-usage hint and the completion list. What is
sent is the string.
Item, block-state and component completion never leaves the machine
either. ItemArgument and BlockStateArgument are registered as
context-aware singletons — meaning the wire form carries no data of its
own and the far side builds the parser against its own
CommandBuildContext, which is The tree on the wire below — so the client holds a real ItemParser and builds a real BlockStateParser against its own registries. Item ids, data
components and block properties complete locally, and so does every argument
type whose suggestion method reads a synced registry.
A node that asks for suggestions by hand almost always asks the server.
This is the part the shape of the code invites you to get backwards — not
that the round trip is common, but that opting in to a provider is what
costs you one. SuggestionProviders.getName returns the registered name for
a SuggestionProviders.RegisteredSuggestion and ask_server for
everything else, so any node whose suggestions come from a plain lambda
serialises as a request. Of the 73 vanilla nodes that attach a provider at all, seven name one of the three providers that mean anything on the wire — three available_sounds, two summonable_entities and two post_effects — and the other 66 become ask_server. That is how /function, /datapack, /bossbar,
/scoreboard, /team, /schedule and /whitelist complete. The other 381 argument nodes attach nothing and fall back to their argument type’s own
suggestions — which is why /give, the command in the line at the top of
this page, never asks the server anything. A second route reaches the same packet:
ClientSuggestionProvider.suggestRegistryElements failing to find a
server-only registry — loot tables, advancements, recipes — and falling
through. Two mechanisms, one packet.
One interface is why an argument type never knows which side it is on.
SharedSuggestionProvider is implemented by CommandSourceStack on the
server and by ClientSuggestionProvider on the client, and it is what an argument type’s suggestions ask of the source, when they ask it anything, so the same code runs on either. Most of it is static helpers — the SharedSuggestionProvider.suggest
and SharedSuggestionProvider.suggestResource families, and the coordinate
suggesters that offer you the block you are looking at — and all but one of its instance methods are the questions the two sides answer differently, among them the tab list, the selected entities, the relevant coordinates, and
SharedSuggestionProvider.customSuggestion, which is the packet on the client
and a completed empty future on the server.
Replies are matched by id, so a stale answer never flashes. The reply,
ClientboundCommandSuggestionsPacket, lands on
ClientPacketListener.handleCommandSuggestions, which forwards it to the
provider. ClientSuggestionProvider.customSuggestion cancels the in-flight future and
increments a counter;
ClientSuggestionProvider.completeCustomSuggestions compares the reply’s id
against that counter and drops anything older. The reply itself is
truncated to a thousand entries, silently — no marker, no message.
Enter parses a second time on the client, for one reason.
ClientPacketListener.sendCommand, which ChatScreen calls, runs SignableCommand.of to find out
whether any argument is a SignedArgument. /give has none, so the plain
packet — ServerboundChatCommandPacket, the string alone — goes; a /msg would take a timestamp, a salt and the last-seen
message set, sign each signable argument and send the signed variant. A
command the player did not type — a dialog button, a chat click event —
goes through ClientPacketListener.sendUnattendedCommand instead and is parsed once or twice more before anything is sent
(permissions).
A sign is the exception that proves the rule: its click command reaches the client only as the sign’s text, and is never checked there, because the server runs it when the sign is used, and only if the sign’s allow_op_features flag is set.
The inbound packets cross the thread boundary differently, and neither takes the ordinary hop. ServerboundCommandSuggestionPacket is handed, on the Netty thread, to a ServerCommandSuggestionsProvider, which keeps only the newest request and answers at most one a tick, parsing it on the Server thread; the command packets do real work on the Netty thread first: ServerGamePacketListenerImpl.tryHandleChat refuses illegal characters there and only then hands the rest to the Server thread.
The hop the other packets take is
the connection
(the server
tick
counts them, and chat and
signing says what each
of those checks catches). What matters for a command is only that the
validation which can disconnect you runs before the parse does — so a command
whose text is illegal never reaches the dispatcher at all.
The authoritative parse is the server’s —
ServerGamePacketListenerImpl.parseCommand — with a CommandSourceStack
from ServerPlayer.createCommandSourceStack carrying the real permission
set, and Brigadier consults each node’s requirement during that parse.
Execution then is not a Java call: Commands.performCommand flattens the
parse into a context chain and hands it to
Commands.executeCommandInContext, which is
the execution engine.
/give itself is unremarkable and instructive. Its lambda calls
GiveCommand.giveItem: EntityArgument.getPlayers resolves the selector, ItemArgument.getItem
hands over the ItemInput that was built during parsing,
ItemInput.createItemStack validates it, and the stacks go through
Inventory.add with anything that will not fit dropped on the floor
(items and stacks). Success goes, through CommandResponseTracker.sendFeedback, to CommandSourceStack.sendSuccess — which takes a supplier, so the message
is never built when nobody will see it — and broadcasts to admins under two
game rules.
Where a command’s output goes
CommandSourceStack carries a CommandSource — a message sink, three questions and a fourth question with a default, and no state — and its implementations are the whole answer to why a command block does not spam chat. MinecraftServer (the console) and RconConsoleSource accept
both success and failure, and RconConsoleSource.shouldInformAdmins defers to
the broadcast-rcon-to-ops property. BaseCommandBlock’s source accepts
success only under GameRules.SEND_COMMAND_FEEDBACK and informs admins only
under GameRules.COMMAND_BLOCK_OUTPUT, which is what the second of those game rules is for; a command block with Track Output off gets no source of its own. Each player has one, and so does /debug function’s tracer.
CommandSource.NULL refuses everything, and the game hands it to several sources with nobody to talk to — a sign running its own click command, a text component being resolved, that command block. The command block is CommandBlock, whose own redstone edge, tick and chain walk decide when it runs, and the CommandBlockEntity that holds a BaseCommandBlock: a stored string and a source built for each run.
Arguments that are recipes, not values
Three argument families do not produce a value at all. They produce
something evaluated against the CommandSourceStack at run time, which is
why one parsed command means different things at different links of an
/execute chain.
Coordinates holds relativity, not a position.
WorldCoordinates is the implementation — three WorldCoordinates, each a
value plus a is this relative flag — and
Coordinates.getPosition resolves it against the source, so ~ means
something different at every link and a single parsed argument yields N
positions in a forked execution. LocalCoordinates (^ ^ ^) is the
interesting one: it builds a basis from the source’s rotation and its
EntityAnchorArgument.Anchor, so it is the only Coordinates shape that
depends on eye height — it is not itself an argument type, and both
Vec3Argument and BlockPosArgument can produce one. SwizzleArgument
parses an axis subset (xz), and /execute align is its only user.
EntitySelector is a compiled query, not a parse tree — thirteen final fields with no grammar in them, assembled by
EntitySelectorParser from EntitySelectorOptions and resolved against a
CommandSourceStack much later, which is why one parsed selector yields a
different set at every link of a chain. ScoreHolderArgument and
GameProfileArgument reimplement the same selector-or-literal fork for their
own value types. The grammar, the twenty-one options, the two-phase
permission check and the two data structures a selector can be resolved
against are entity selectors.
FunctionArgument reads an id and nothing else, deferring the lookup to
execution — which is what lets a function be compiled against a null server
(functions and macros).
The parser under the parser
Five argument types and the whole SNBT reader are not hand-written
StringReader walks. They are grammars, written against
net/minecraft/util/parsing/packrat — Mojang’s own parser-combinator
framework, with Term as the combinator algebra, Dictionary and
NamedRule binding named productions, Scope as the typed capture
environment, and CachedParseState as the memo table keyed by position and
rule. That memo table is the packrat in the name: a backtracking grammar
that would otherwise re-parse the same prefix once per alternative looks it
up instead.
The reason it matters to a command page is ErrorCollector and
SuggestionSupplier: the grammar produces completions as a by-product of
failing. A hand-written argument type can only suggest at a token boundary
it thought to check; a grammar knows every terminal that could have
continued the parse, which is why /clear @s minecraft:diamond_sword[…
still completes mid-token. The consumers are exactly nine —
ComponentArgument, NbtTagArgument, ResourceOrIdArgument,
StyleArgument, ItemPredicateArgument, ComponentPredicateParser, and
TagParser / SnbtGrammar / SnbtOperations on the NBT side
(codecs, NBT and JSON).
The tree on the wire
What the packet carries
Commands.sendCommands makes a deep copy of the dispatcher’s tree, filtered
by each node’s requirement for that player’s source
(Commands.fillUsableCommands), and serialises it. An argument node carries
the registry id of its ArgumentTypeInfo plus whatever the template writes:
usually nothing (SingletonArgumentInfo writes zero bytes), often a flags
byte from ArgumentUtils.createNumberFlags or EntityArgument’s
single / players-only pair, sometimes a registry key. The client then
builds real parsers from those templates against its own
CommandBuildContext, which is why a data pack’s biomes and dialogs are
parseable on the client for free.
Six argument types for one idea
One family is worth naming because a reader meets it constantly and never
under one name. ResourceArgument, ResourceKeyArgument,
ResourceOrIdArgument, ResourceOrTagArgument, ResourceOrTagKeyArgument
and ResourceSelectorArgument are six argument types over the same idea — an id resolved against a registry, most of them through the CommandBuildContext — differing
in what they hand back (a Holder, a bare ResourceKey, an inline literal,
a tag as well as an element) and in whether they accept a glob. Only
ResourceSelectorArgument does, which is why /test run * works and
/give @s * does not. Beneath them, the five classes of
commands/synchronization/brigadier are the wire descriptions of Brigadier’s
own primitive types — StringArgumentSerializer and the four ArgumentTypeInfos for integer, long, float and double; with the boolean, which needs no class of its own, they are the only argument types in the game the game did not write.
What is registered, and what is on the wire
Commands.validate is what keeps that honest — though only in development:
Bootstrap calls it under SharedConstants.IS_RUNNING_IN_IDE alone, so a
shipped client never runs it. It throws if any registered argument type is
missing from ArgumentTypeInfos. The two numbers a reader will want do not
count the same thing: forty argument-type classes sit directly in net/minecraft/commands/arguments, with more in its blocks, item,
coordinates and selector subpackages, while sixty-two entries are registered in ArgumentTypeInfos — a class may register several: the six resource types above are twelve entries, ResourceOrIdArgument registering seven through its nested subclasses.
Two surprises, and what /reload does not send
Two things about ClientboundCommandsPacket surprise people. It has exactly one call
site, PlayerList.sendPlayerPermissionLevel, so the tree and the op-level
entity event are always sent together — on join, respawn, a dimension
change, op and deop, and the singleplayer toggles for cheats and guest command access, and not after /reload.
And an unknown argument type deletes the node and everything under it: a
modded type reaching a vanilla client decodes to a null stub,
ClientboundCommandsPacket.NodeResolver substitutes a bare
RootCommandNode, the children are resolved and attached to that throwaway
root, and the parent then skips any child that is a RootCommandNode — so
the node and everything under it vanish from the tree the player can see.
The packet is not rejected, as long as the unknown type wrote no bytes of its own and its node names no suggestion provider. What the filtering means, and the second
elision that rides the same packet, is permissions.
One more thing crosses the wire from here and belongs to nobody else:
ClientboundCustomChatCompletionsPacket, a server pushing arbitrary completions for ordinary chat messages into the chat box, add / remove / set.
/reload builds a whole new Commands and a whole new dispatcher inside
ReloadableServerResources and tells nobody. The consequence is narrower
than the folklore on either side: both server-side parses read through
MinecraftServer.getCommands and pick up the new dispatcher immediately, so
a newly added function does tab-complete after a reload — that completion
is an ask_server round trip. What goes stale on the client is the tree’s
shape and its flags, which no vanilla data pack can change — and the tree is
not re-sent, because its only sender is the permission-level call above
(the resource system
owns the rest of what /reload does).
Commands that are a door to somewhere else
Most of net/minecraft/server/commands — the atlas has its size — is a thin lambda over machinery another page of this book owns. A
reader looking for “how does /locate work” wants the mechanism page, so
here is the index.
| command | what it really reaches | where that lives |
|---|---|---|
LocateCommand | three barely related parts: LocateCommand.locateStructure can drive world generation on the server thread, because deciding whether a structure is at a chunk means asking the structure check; LocateCommand.locateBiome asks the biome source and never reads a stored palette; LocateCommand.locatePoi asks the POI index | structure placement, biomes |
FillBiomeCommand | writes the biome palette of the affected sections and resends them — the only command that edits a chunk’s biomes | chunk anatomy |
PlaceCommand | four doors: a feature with no placement layer, by id or inline, a whole structure, jigsaw assembly directly, and a structure template with rotation, mirror, integrity and a seed | features and placement, jigsaw and templates |
LootCommand, ItemCommands | ItemCommands.applyModifier runs a loot function over an existing stack — /item modify, and the from … <modifier> forms of /item replace, fill and override. A slot is named in the numbering player anatomy decodes, and /item also takes a range of them or a slot source by id or inline. Both take a table or modifier through ResourceOrIdArgument, so an inline literal works where an id does | contexts and predicates for which set each runs in, loot tables for the functions themselves |
EnchantCommand, ExperienceCommand | thin faces over two systems | enchanting, hunger and experience |
ExecuteCommand, FunctionCommand | not commands so much as the front end of the engine | the execution engine, and functions and macros for what /function hands it |
ScoreboardCommand, TeamCommand, TriggerCommand, DataCommands | the commands that write the scoreboard and stored NBT, beside execute store | scores, teams and stored data |
And one class of command a reader will look for in a shipped game and not
find. Commands registers DebugConfigCommand, RaidCommand,
DebugPathCommand, DebugMobSpawningCommand, WardenSpawnTrackerCommand,
SpawnArmorTrimsCommand and ServerPackCommand only when
SharedConstants.DEBUG_DEV_COMMANDS or SharedConstants.IS_RUNNING_IN_IDE
is set, and ChaseCommand behind a flag of its own. DebugConfigCommand is
additionally registered only in a dedicated server’s command set, which matters: it is the only vanilla
caller of the play-to-configuration transition and back
(protocol phases).
Signed arguments, in one paragraph
MessageArgument is the only signed argument in the game — the sole
implementor of SignedArgument — and seven command classes register it
under ten literals a player can type: /ban-ip, /ban, /me, /kick,
/say, /msg with its /tell and /w redirects, and /teammsg with
/tm. All of
SignableCommand, ArgumentSignatures, ArgumentVisitor and
CommandSigningContext exists to serve them: ArgumentVisitor.visitArguments
walks a parse to find which arguments need a signature, and the map carries
them afterwards. A command with signable arguments sent unsigned is
refused outright when the server enforces secure profiles, and a signature
that does not match the parse breaks the player’s whole message chain
(chat and signing).
Where to look
Commands for what exists and what a failed parse is called;
CommandSourceStack for what a command knows; ArgumentTypeInfos for the
catalogue of argument types and their wire forms; EntitySelectorOptions for the grammar players write; CommandSuggestions for everything
that happens while you type; and ClientboundCommandsPacket for the one
place the two sides agree on a shape.
Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes tools/verify_names.py.