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
| class | what it decides | when it runs |
|---|---|---|
EntitySelectorParser | the 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 themselves | parse time |
EntitySelectorOptions | the name-to-handler map — twenty-one entries, filled once by Bootstrap.bootStrap and never again | bootstrap |
InvertableSetOptionState | the three-state machine behind type=!zombie,type=!skeleton: one positive assertion or any number of negations and tags, never both | parse time |
SetOnceOptionState | one 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 field | parse time |
EntitySelector | the compiled query: thirteen final fields, and no grammar left in them | built at parse time, run later |
EntityArgument | four argument shapes (single or many, entities or players) and the parse-time rejections that enforce them | parse time and on the wire |
CommandSourceStack | the only thing a selector can be resolved against — origin, level, server, permission set | resolve time |
LevelEntityGetterAdapter | the 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.
| head | limit | non-players | order | extras |
|---|---|---|---|---|
| @a | unbounded | no | arbitrary | typed to player |
| @e | unbounded | yes | arbitrary | adds an Entity.isAlive test |
| @n | 1 | yes | nearest | adds an Entity.isAlive test |
| @p | 1 | no | nearest | typed to player |
| @r | 1 | no | random | typed to player |
| @s | 1 | yes | arbitrary | resolves 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.
| option | what its handler does | repeatable? |
|---|---|---|
| name | compares Nameable.getPlainTextName | one positive, or many negatives |
| team | compares Entity.getTeam — the empty string means no team | one positive, or many negatives |
| gamemode | compares ServerPlayer.gameMode, and drops non-players | one positive, or many negatives |
| type | an id sets the EntityTypeTest; a tag only adds a test | one positive id, or many negatives and tags |
| tag | reads Entity.entityTags — empty means no tags at all | freely |
| nbt | serialises the candidate and compares with NbtUtils.compareNbt | freely |
| predicate | runs a loot condition in LootContextParamSets.SELECTOR (contexts and predicates) | freely |
| scores | a brace map of objective to MinMaxBounds.Ints | once |
| advancements | a brace map of advancement to done-ness, players only | once |
| limit | sets the result cap, rejects anything below 1 | once, never on @s |
| sort | picks one of four orders | once, never on @s |
| distance | a MinMaxBounds.Doubles, rejects negatives, world-limits | once |
| level | a MinMaxBounds.Ints, rejects negatives, drops non-players | once |
| x, y, z | override one axis of the resolve origin, world-limit | once each |
| dx, dy, dz | build the box, world-limit | once each |
| x_rotation, y_rotation | angle ranges that wrap through 360 | once 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
InvertableSetOptionStateand fourSetOnceOptionState— 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.