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

Entity selectors

Verified against Minecraft 26.3 · Part XIII · you type /kill @e[type=!player,distance=..8,sort=nearest,limit=1], and the characters inside the brackets decide, between them, which levels are searched and which of two entirely different data structures answers.

Put a command block at the Overworld origin and give it /tp @p 0 100 0. One player is two hundred blocks away across the Overworld; another is standing on the Nether roof directly “above” the block, at the Nether’s own 0, 128, 0. The command block teleports the one in the Nether. @p is not “the nearest player in this world”: it is the player whose raw x, y and z are nearest, chosen from the list of everybody on the server, compared as though the dimensions were stacked in the same coordinate space. (Type the same command yourself and you always win it — a player’s own source sits at distance zero from a player’s own position.) Nothing in a selector is confined to one level unless you write one of seven options that says so — and four of those seven, distance and the three box deltas, are also what decides whether the game walks every entity in the level or asks the chunk sections for a box.

That is the shape of the whole subject. A selector looks like a filter language, and it is one, but many of its twenty-one option names do more than filter: they write the query plan. This page is about which is which.

The cast

classwhat it decideswhen it runs
EntitySelectorParserthe reader, the grammar and thirty-one half-built fields besides it. It owns the selector’s own syntax; the brace grammars of scores and advancements are read by the handlers themselvesparse time
EntitySelectorOptionsthe name-to-handler map — twenty-one entries, filled once by Bootstrap.bootStrap and never againbootstrap
InvertableSetOptionStatethe three-state machine behind type=!zombie,type=!skeleton: one positive assertion or any number of negations and tags, never bothparse time
SetOnceOptionStateone boolean, for the four options with no parsed field to test for emptiness — limit, sort, scores, advancements. The other ten once-only options enforce it by checking their own fieldparse time
EntitySelectorthe compiled query: thirteen final fields, and no grammar left in thembuilt at parse time, run later
EntityArgumentfour argument shapes (single or many, entities or players) and the parse-time rejections that enforce themparse time and on the wire
CommandSourceStackthe only thing a selector can be resolved against — origin, level, server, permission setresolve time
LevelEntityGetterAdapterthe fork in the road: an EntityLookup walk, or an EntitySectionStorage box query (entity lifecycle owns both)resolve time

net/minecraft/commands/arguments/selector holds five classes, and every one of them is in the server jar and the client jar. That matters later.

Three stages, and the last one is not on the parser’s clock

flowchart LR
    S["the argument text"]
    subgraph P["PARSE — at parse time"]
        direction TB
        P1["the head sets the defaults"]
        P2["each option name finds its handler"]
        P3["the handler writes a field or adds a test"]
        P1 --> P2 --> P3
    end
    subgraph C["COMPILE — at parse time"]
        direction TB
        C1["thirteen final fields"] ~~~ C2["a box, or none"] ~~~ C3["an origin"]
    end
    subgraph R["RESOLVE — per execution"]
        direction TB
        R1["which levels"]
        R2["which of two structures"]
        R3["order, then cut to the limit"]
        R1 --> R2 --> R3
    end
    S --> P
    P --> C
    C --> R

The three stages a selector passes through — the first two while Brigadier parses, and the third on the Server thread each time a command asks for its argument; the three boxes under COMPILE are what it produces, not steps.

The first two stages happen while Brigadier walks the command tree; the third happens when the command’s lambda asks for its argument. In between, the selector is an ordinary immutable object sitting in a parsed context, which is why an /execute chain can resolve the same parsed selector once per source and get a different answer each time (the execution engine).

Parse: six heads and twenty-one names

EntitySelectorParser.parseSelector reads the character after the @ and sets four things — a result limit, whether non-players are in scope, an order, and sometimes a type. There are six, and no more: the default branch of that switch throws.

headlimitnon-playersorderextras
@aunboundednoarbitrarytyped to player
@eunboundedyesarbitraryadds an Entity.isAlive test
@n1yesnearestadds an Entity.isAlive test
@p1nonearesttyped to player
@r1norandomtyped to player
@s1yesarbitraryresolves to the source’s own entity

Only @e and @n add that test, and only LivingEntity.isAlive makes it mean anything — it is the override that adds “and has health left” to the base class’s “and has not been removed”. So a player sitting on the death screen is invisible to @e and @n, and still a target for @a, @p and @r — though twenty ticks after death the body leaves the level’s own player list, so a world-limited selector stops finding it.

Then the bracket. EntitySelectorParser.parseOptions reads a name, looks it up through EntitySelectorOptions.get, and hands the reader to the handler it finds. Twenty-one names are registered, counted by reading every EntitySelectorOptions.register call in EntitySelectorOptions.bootStrap.

optionwhat its handler doesrepeatable?
namecompares Nameable.getPlainTextNameone positive, or many negatives
teamcompares Entity.getTeam — the empty string means no teamone positive, or many negatives
gamemodecompares ServerPlayer.gameMode, and drops non-playersone positive, or many negatives
typean id sets the EntityTypeTest; a tag only adds a testone positive id, or many negatives and tags
tagreads Entity.entityTags — empty means no tags at allfreely
nbtserialises the candidate and compares with NbtUtils.compareNbtfreely
predicateruns a loot condition in LootContextParamSets.SELECTOR (contexts and predicates)freely
scoresa brace map of objective to MinMaxBounds.Intsonce
advancementsa brace map of advancement to done-ness, players onlyonce
limitsets the result cap, rejects anything below 1once, never on @s
sortpicks one of four ordersonce, never on @s
distancea MinMaxBounds.Doubles, rejects negatives, world-limitsonce
levela MinMaxBounds.Ints, rejects negatives, drops non-playersonce
x, y, zoverride one axis of the resolve origin, world-limitonce each
dx, dy, dzbuild the box, world-limitonce each
x_rotation, y_rotationangle ranges that wrap through 360once each

Three of the twenty-one are freely repeatable — tag, nbt and predicate — because they are the three registered as always available, with no state object behind them at all. That is why tag=a,tag=b is the idiom for “has both” and type=zombie,type=skeleton is a parse error rather than an empty result: InvertableSetOptionState moves to a terminal state the moment a positive id is accepted, and EntitySelectorOptions.get then refuses the whole option by name before its handler ever runs. Its other terminal state is the permissive one — after a negation or a tag, more negations and more distinct tags are allowed, which is why type=!zombie,type=!skeleton works and why two entity tags can be written together and AND.

Compile: what a box is, and where it comes from

EntitySelectorParser.getSelector runs once, at the end of the parse, and turns the pile of fields into thirteen final ones. Two of the thirteen are the interesting decisions.

The box. If any of dx, dy or dz was written, EntitySelectorParser.createAabb builds the box from those three, treating the missing ones as zero and adding one to each maximum — a dx=0 volume is one block wide, not zero. Otherwise, if distance was written and has a maximum, the box is a cube of that radius, again with one added to the positive corner. Otherwise there is no box. So distance=8.. — a minimum with no maximum — produces no box at all, and dx=3,distance=..64 ignores the distance entirely when choosing the box, because the delta branch wins.

The origin. If any of x, y or z was written, the position becomes a function that overrides those axes of the source’s position and keeps the rest. Otherwise it is the identity. This is applied per execution, which is what makes x=0 mean the same thing everywhere while every axis left unwritten follows the source at each link of a chain.

Everything else that was written is already a test in a list, in written order — with exceptions appended afterwards whatever order they appeared in. EntitySelectorParser.finalizePredicates adds the two rotation tests and the experience-level test last, and EntitySelector.getPredicate then appends up to three more at resolve time: the feature-flag test, the exact box test and the range test. Util.allOf evaluates them in that order and short-circuits, so the range test, a few multiplications, runs after an nbt comparison that serialised the whole entity.

Resolve: which levels, which structure, which order

flowchart TD
    A["EntitySelector.findEntities"] --> C{"a bare name or a UUID?"}
    C -- name --> N["PlayerList.getPlayerByName"]
        C -- UUID --> UE["Level.getEntity, level by level"]
    C -- neither --> D{"is it the source itself?"}
    D -- yes --> S["the source's own entity, if it passes"]
    D -- no --> E{"world-limited?"}
    E -- "yes: this level" --> B2{"non-players in scope?"}
    E -- "no: every level" --> B2
    B2 -- no --> PL["a player list, walked"]
    B2 -- yes --> H{"is there a box?"}
    H -- yes --> I["EntitySectionStorage — only the sections near the box"]
    H -- no --> J["EntityLookup — every visible entity, one by one"]
    I --> K["sort unless arbitrary, then cut to the limit"]
    J --> K
    PL --> K

How a compiled selector finds its entities — the players-only selector asks the same questions in the same order, so the figure asks whether non-players are in scope only where the two answers part; the three shortcuts at the top return at once, with no sort and no limit.

Three shortcuts come first. A bare player name goes to PlayerList.getPlayerByName, since a name always takes non-players out of scope; a UUID is looked up with Level.getEntity, level by level, since a UUID always brings non-players into scope; and @s tests the source’s own entity against the selector’s tests. None of the three is sorted or cut.

World-limited is a parse-time flag, not a runtime one. Exactly seven option handlers call EntitySelectorParser.setWorldLimited: distance, x, y, z, dx, dy and dz. Write none of them and EntitySelector.findEntities iterates every level the server has. /kill @e[type=item] is a three-dimension operation.

The two structures are genuinely different. With a box, Level.getEntities goes through EntitySectionStorage, which visits only the accessible non-empty 16-cubes the box overlaps once widened by two blocks sideways and four below. Without one, ServerLevel.getEntities goes through EntityLookup, which walks the level’s entire visible-entity map and calls EntityTypeTest.tryCast on each — EntityTypeTest being the small “is this the type I asked for, and give it to me typed” interface the whole entity-fetching API is generic over. There is no index by entity type. type=zombie narrows nothing structurally; it is a cast applied one entity at a time, ahead of the tests. Of the seven, only distance and the three deltas narrow the entity walk within a level, and only when they add up to a box; x, y and z only confine it to one level and move the box.

And the box path finds things the walk cannot. Level.getEntities also offers each ender dragon’s eight EnderDragonPart sub-entities to the type test and the predicate, and every part reports its parent’s type. So a box query in the End can return the dragon and up to eight more “ender dragons”; the same selector without a box returns one.

Players never get a box. Both player paths — ServerLevel.getPlayers for one level, PlayerList.getPlayers for the whole server — are linear walks of a list, and the box survives only as one more test. @a[distance=..8] costs what @a costs.

Sort is what takes the limit away. EntitySelector.getResultLimit returns the parsed limit only when the order is arbitrary, and the unbounded value otherwise, because a sort has to see everything before it can know what comes first. When it is the parsed limit, it reaches the level query as an early abort. So @e[limit=1] stops at the first match, and @e[limit=1,sort=nearest] collects every match in range, sorts the list and throws all but one away. @n and @p live in the second mode unless told otherwise: their heads set the nearest order, so they collect first and cut afterwards, and only an explicit sort=arbitrary puts them back in the first.

So the query plan is written by thirteen of the twenty-one names: the seven above, which world-limit the search and build or place the box; limit, which aborts it early; sort, which un-decides part of it by taking the limit away; and the four that drop non-players, level, gamemode, advancements and a positive type=player, which send the whole search to a player list. The other eight only filter what the plan returns.

One permission, checked in two places, for two different reasons

The gate is a single atom, Permissions.COMMANDS_ENTITY_SELECTORS, granted by LevelBasedPermissionSet from gamemaster upward as the one hard-coded exception in that interface (permissions). It is the only permission in the game checked in two different phases, it is read in seven places, all of them under commands/arguments, and they divide cleanly in two.

At parse time, EntitySelectorParser.allowSelectors asks the source and the answer becomes a constructor argument. If it is false, an @ throws EntitySelectorParser.ERROR_SELECTORS_NOT_ALLOWED immediately, and a bare name or UUID still parses. This is the check that matters for the three vanilla commands that take a selector-capable argument with no permission requirement at all — MsgCommand, EmoteCommands and TeamMsgCommand, so /msg, /tell, /w, /me, /teammsg and /tm. For an ordinary player this atom is the only gate on those.

MessageArgument alone treats a refusal as a formatting decision rather than an error: without the permission the message is taken as literal text, so an unopped /msg Bob @a sends those two characters. With the permission, an @ that is not a valid selector head is skipped and the scan continues — which is how an address like me@gmail.com survives — but a malformed selector body throws, and the whole command fails to parse.

At resolve time, EntitySelector.checkPermissions asks again, guarding on EntitySelector.usesSelector, which only EntitySelectorParser.parseSelector ever sets. A selector compiled from a bare player name is exempt. The second check is not redundant, because there is a whole route into the machinery that never passed the first one: EntitySelector.COMPILABLE_CODEC compiles a selector out of a text component — the selector content type, the score name field and the entity NBT data source — and does so with selectors unconditionally allowed, because a codec has no source to ask. The resolve-time check is what decides whether a /tellraw written by a data pack may enumerate entities, and it asks the source the component is being resolved against, never whoever wrote it.

Two more argument types write the same fork by hand

EntityArgument is not the only argument type that will take an @. ScoreHolderArgument and GameProfileArgument each reimplement the selector-or-literal fork for a value that is not an entity, and what they do on the literal side is the interesting half.

GameProfileArgument resolves to profiles, not entities. An @ builds a selector and is rejected outright if it includes non-players; anything else is read to the next space and looked up in the server’s name-to-id cache — so /ban, /whitelist and /op name a player who has never joined this session and may not exist on this server at all. That lookup is the only thing on this page that leaves the game’s own data.

ScoreHolderArgument resolves to ScoreHolders, which need not be alive. Its literal side has four branches in order — * for every tracked holder, a #-prefixed name taken as a bare string, a UUID searched across every level, and an online player — with the UUID and player branches falling back to a bare name and the wildcard alone throwing when there is nothing to return (scores, teams and stored data for what those names are for).

Both take the permission the same way this page’s own parse does, and both check the compiled selector rather than the text, as EntityArgument does — ScoreHolderArgument for its single-or-many shape, GameProfileArgument for players only.

Four argument shapes, enforced on the query and not on the text

EntityArgument comes in four shapes — one entity or many, players only or anything — and it enforces the difference on the compiled selector, during the parse, before the world is touched. A limit above one in a single-target slot is refused, and so are non-players in a players-only slot.

That is why /damage @e 1 complains before it has looked at anything: /damage, /ride and /data get entity all take the single-target shape, and @e is unbounded. /kill @e is fine, because /kill takes the many-entities shape and neither rejection can fire on it. And @s is exempt from the players-only test, so /msg @s parses and then finds nobody when the source is not a player.

The client parses selectors and cannot resolve one

This is what the cast’s five-classes-in-both-jars is for. The client parses a selector by two routes and can run neither.

EntityArgument.listSuggestions builds a real EntitySelectorParser against the client’s own permission set, parses as far as it can, swallows the exception and asks the half-finished parser for its suggestions — which is why completion inside a bracket knows which options are still legal; the tree it parses against is a real one the server sent (Brigadier and commands). And ComponentSerialization decodes a selector content type on the client with the same compiler the server uses.

What the client cannot do is run one: every find method takes a CommandSourceStack, and the client’s suggestion source is a ClientSuggestionProvider. The one place a client resolves a component that might contain a selector is ServerStatusPinger, whose ResolutionContext deliberately carries no source, so a server-list description containing a selector renders as nothing at all.

Questions players ask

Which dimension does /kill @e[type=item,limit=1] kill in? The overworld, unless there is nothing to kill there. With no world-limiting option the resolve walks MinecraftServer.getAllLevels, which iterates the server’s LinkedHashMap of levels in the order they were created — the overworld put in first, the rest in registry order — and an arbitrary-order limit reaches the level query as an early abort, so the walk stops at the first match it finds. Write sort=nearest and the bias disappears, because a sort has to see every level before it can rank anything.

What does the client suggest for an entity argument? Online player names, plus — from ClientSuggestionProvider.getSelectedEntities — the UUID of whatever your crosshair is on. Point at a cow, press tab, and it offers you that cow.

Is sort=random seeded? No. It is the JDK’s list shuffle, with no world seed and no RandomSource anywhere near it, so @r is not reproducible from a save.

Why does distance=..8 not return something 8.9 blocks away? Because the box is only a pre-filter. The cube built for a maximum of 8 spans −8 to +9 on each axis, deliberately larger than the sphere it approximates, and the exact test that follows is MinMaxBounds.Doubles.matchesSqr, which compares squared distances against pre-squared bounds and so never takes a square root.

For a 1.21-era reader. The parser’s crowd of hasNameEquals / hasNameNotEquals / hasGamemodeEquals booleans is gone, replaced by eight state objects — four InvertableSetOptionState and four SetOnceOptionState — that enforce the same rules structurally.

Where to look

EntitySelector first — thirteen fields and four find methods, and the design is in them. Then EntitySelectorParser.getSelector for the one place those thirteen are decided, and EntitySelectorOptions.bootStrap for the grammar players write. LevelEntityGetterAdapter is six methods long and is where the cost of every selector is settled. Note that the name EntitySelector is used twice in the game: this one, and an unrelated bag of predicate constants in world/entity that the mob AI and the hoppers use.


Rules: names, never code · how the system works, not how the code reads · newest version only · every backticked name passes tools/verify_names.py.