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.2 · 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 the tree the client rebuilt says, node by node, who is allowed to answer. Vanilla registers 459 argument nodes, and only 62 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 nodes take it: they are the ones that complete 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 three named providers a node may ask for. Everything else serialises as ask_serverboth
ClientSuggestionProviderthe client’s source: the tab list, the looked-at block and entity, and the one method that sends a packetclient
CommandSuggestionsthe 688-line 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
    participant CSug as CommandSuggestions
    participant CPL as ClientPacketListener
    participant CSP as ClientSuggestionProvider
    participant SGPL as ServerGamePacketListenerImpl
    participant Cmds as Commands
    participant GC as GiveCommand

    CSug->>CPL: parse the whole line against the client's dispatcher, every keystroke
    CSug->>CPL: getCompletionSuggestions on that parse, with CSP as the source
    CSP->>SGPL: ServerboundCommandSuggestionPacket — only if the node asks the server
    SGPL->>CPL: ClientboundCommandSuggestionsPacket — capped at a thousand, id-matched
    CPL->>SGPL: ServerboundChatCommandPacket — the raw string, no signatures for /give
    Note over SGPL: the illegal-character check runs on the Netty thread
    SGPL->>Cmds: hand to the server thread, then parse again with the player's real source
    Cmds->>Cmds: the node requirement is consulted inside the parse
    Cmds->>Cmds: performCommand — one queue, limits read from the level's game rules
    Cmds->>GC: the registered lambda — resolve the selector, read back the ItemInput
    GC->>GC: Inventory.add, then sendSuccess and a broadcast to admins

Each arrow 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, and that parse produces the red underline, 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, so the client instantiated a real ItemParser and 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 67 vanilla nodes that attach a provider at all, five name one of the three registered providers and the other 62 become ask_server. That is how /function, /datapack, /bossbar, /scoreboard, /team, /schedule and /whitelist complete. The other 392 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.

Replies are matched by id, so a stale answer never flashes. 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 runs SignableCommand.of to find out whether any argument is a SignedArgument. /give has none, so the plain packet 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 click event, a sign — goes through ClientPacketListener.sendUnattendedCommand instead and is parsed twice more before anything is sent (permissions).

The two inbound packets cross the thread boundary differently, on purpose. ServerboundCommandSuggestionPacket goes through PacketUtils.ensureRunningOnSameThread, so its parse happens on the main thread. ServerboundChatCommandPacket does not: ServerGamePacketListenerImpl.tryHandleChat runs the character-legality check on the Netty thread — and may disconnect from there — before handing the body to MinecraftServer.execute, calling ServerPlayer.resetLastActionTime on the way past, so a little ServerPlayer state really is written off the main thread. The signed variant does more still: ServerGamePacketListenerImpl.handleSignedChatCommand unpacks the last-seen message set under a lock, and can disconnect for chat-validation failure, before the legality check runs. This is the principle the connection describes — cheap validation early — with the honest qualifier that “cheap” here includes two disconnect paths and one field write.

The authoritative parse is the server’s, 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. 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 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.

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. 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 reader and 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

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.

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. Thirty-eight argument-type classes live in the top net/minecraft/commands/arguments package, plus the blocks, item, coordinates and selector subpackages; fifty-seven are registered on the wire.

Two things about that packet 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 four LAN toggles, and not after /reload. And an unknown argument type deletes the node, not its children: 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. What the filtering means, and the second elision that rides the same packet, is permissions.

/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.

Commands that are a door to somewhere else

Most of net/minecraft/server/commands — a hundred classes and 12,800 lines — is a thin lambda over machinery another part 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 configured feature with no placement layer, 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> form of /item replace. Both take a table or modifier through ResourceOrIdArgument, so an inline literal works where an id doesloot tables
EnchantCommand, ExperienceCommandthin faces over two systemsenchanting, hunger and experience
ExecuteCommand, FunctionCommandnot commands so much as the front end of the enginethe execution engine
ScoreboardCommand, TeamCommand, TriggerCommand, DataCommandsthe entire write surface of the scoreboard and of stored NBTscores, 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 dedicated-server-only, which matters: it is the only vanilla caller of the play-to-configuration transition and back (protocol phases).

One more thing crosses the wire from here and belongs to nobody else: ClientboundCustomChatCompletionsPacket, a server pushing arbitrary non-command completions into the tab list, add / remove / set.

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 actually 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.