Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

classwhat it decidesside
Commandsthe one server-side CommandDispatcher, every registration, and what a failed parse is calledserver
CommandSourceStackwho is running this, from where — position, rotation, level, entity, permissions, output sink. Immutable; a change returns a copyserver
CommandBuildContextthe registries an argument type parses against, so a data pack’s biome is completable with no code changeboth
ArgumentTypeInfosthe wire description of an argument type: a ArgumentTypeInfo.Template that can be written to a buffer and instantiated on the far sideboth
SuggestionProvidersthe four providers that have a name on the wire — ask_server, available_sounds, summonable_entities and post_effects. Everything else serialises as ask_server tooboth
SharedSuggestionProviderthe 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 happensboth
ClientSuggestionProviderthe client’s implementation of it: the tab list, the looked-at block and entity, and the one method that sends a packetclient
CommandSuggestionsthe widget over it — the highlighter, the usage hint, the popup and the parse cacheclient
BrigadierExceptionsinstalled once into Brigadier’s global exception provider, which is why a parse error is a translatable Componentboth

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.

commandwhat it really reacheswhere that lives
LocateCommandthree 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 indexstructure placement, biomes
FillBiomeCommandwrites the biome palette of the affected sections and resends them — the only command that edits a chunk’s biomeschunk anatomy
PlaceCommandfour 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 seedfeatures and placement, jigsaw and templates
LootCommand, ItemCommandsItemCommands.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 doescontexts and predicates for which set each runs in, loot tables for the functions themselves
EnchantCommand, ExperienceCommandthin faces over two systemsenchanting, hunger and experience
ExecuteCommand, FunctionCommandnot commands so much as the front end of the enginethe execution engine, and functions and macros for what /function hands it
ScoreboardCommand, TeamCommand, TriggerCommand, DataCommandsthe commands that write the scoreboard and stored NBT, beside execute storescores, 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.