Contexts and predicates
Verified against Minecraft 26.3 · Part VII · A command asks is this true, here, of this entity? — and the machinery that answers is the same machinery that decides what a chest contains.
/execute if predicate example:in_the_rain run say wet is a question about
the world, and the world is not something a data-pack file may look at.
Before anything can be tested, the server has to turn here into something a
JSON file is allowed to interrogate: a position, maybe an entity, nothing
else. It does that by building a small typed map of parameters and
handing it to an object loaded out of a registry, which returns a boolean.
That machinery is usually called the loot system, because most of it lives
in net/minecraft/world/level/storage/loot — but the keys and the sets it
is built from live in net/minecraft/util/context and know nothing about
loot at all. Seventeen of the thirty-one parameter sets never roll a loot
table, and the set that is enforced is always the caller’s: a loot
table’s own declared type is read only by the load-time validator, once a
reload, and never consulted by a roll.
Two packages, one machine
The machine is five classes in two packages, and which class sits in which package is the fact the rest of the page hangs off.
flowchart TD
Callers["block breaks, deaths, chests, commands, selectors, advancements, trades, enchantment effects, fuel, composters"]
Client["SlotDisplayContext, on the client"]:::client
subgraph U["util/context"]
CK["ContextKey, an Identifier plus a static type"]
CKS["ContextKeySet, the required keys and the allowed"]
CMap["ContextMap, the checked bag of values"]
end
LPar["LootParams, the immutable inputs"]:::server
LCtx["LootContext, one invocation"]:::server
CK -->|"declared required or optional by ContextKeySet.Builder"| CKS
CKS -->|"ContextMap.Builder.buildAndValidate validates against it"| CMap
Callers -->|"each brings its own key set"| CKS
Client -->|"builds a ContextMap of its own"| CMap
CMap -->|"wrapped, with a ServerLevel beside it"| LPar
LPar -->|"plus a random source and a resolver"| LCtx
Every arrow leads down toward a checked map or the invocation built on it, and the box is the whole of the general-purpose half. The three classes inside it name no item, block, entity or loot table; the two below it are the loot package, and everything that asks a question is outside on the right.
The dividing line is the whole argument of this page. ContextKey,
ContextKeySet and ContextMap are three small general-purpose classes in
net/minecraft/util/context with no dependency on items, blocks, entities
or loot. Everything below them is in the loot package, and everything
above them is whoever wants a question answered. Whatever rolls a loot table is one such caller. The others are commands, entity selectors, advancement triggers,
villager trades, every conditional enchantment effect, a furnace’s or brewing stand’s fuel
and a composter’s layers — and, on the client, SlotDisplayContext, which
builds a ContextMap of its own so a recipe book entry can resolve itself
into stacks. World generation builds one too, in NoiseChunk, with no key
set at all: it is how the blender and the beardifier reach the density
functions.
The cast
| class | package | what it owns | when |
|---|---|---|---|
ContextKey | util/context | one parameter’s name and its Java type, and nothing else | — |
ContextKeySet | util/context | which keys a call site must supply, and which it may | built once at class-init |
ContextMap | util/context | the values, and the only place the contract is enforced | Server for loot; the Render thread builds its own for a recipe display, and world generation one per NoiseChunk |
LootParams | storage/loot | the ServerLevel, the map, the dynamic-drop callbacks, the luck | Server |
LootContext | storage/loot | one invocation: the random source, the reference resolver, the visited stack | Server |
LootItemCondition | storage/loot/predicates | a predicate over a LootContext — the boolean answer | Server |
ContextFloatProvider | storage/loot/providers | a float over a LootContext — the numeric answer, beside ContextIntProvider for an int | Server |
ValidationContext | storage/loot | at load, whether an element asked for a key its set does not have | the worker pool |
A key is a name with a type welded to it
ContextKey is an Identifier and a phantom type parameter. It has no
value, no default, no validation and no registry — ContextKey.vanilla
just makes one in the minecraft namespace, and only twenty-one exist: the
sixteen static fields of LootContextParams, the one on SlotDisplayContext
that lets a client draw a recipe, and four that world generation declares on
Blender and Beardifier. The type parameter is what
makes the rest of the system safe: LootContextParams.ORIGIN is a key of
Vec3, LootContextParams.TOOL a key of ItemInstance (the read-only
item view), LootContextParams.ENCHANTMENT_LEVEL a key of a boxed int, and
a reader gets that type back without a cast.
Those twenty-one keys are Java, and a data-pack author never types one of
their names. What a JSON file writes instead is a target — this,
attacker, target_entity, tool, block_entity — and LootContextArg is
the small translator between the two, resolving a target to a key out of
three enums nested in LootContext: LootContext.EntityTarget with six
entity keys, LootContext.BlockEntityTarget and
LootContext.ItemStackTarget with one each. One slot source,
RangeSlotSource, adds a target outside them, container, for
LootContextParams.CONTAINER.
All three enums resolve through LootContextArg.SimpleGetter, which reads the map
with LootContext.getOptional, the one way a context hands out a parameter’s value. A target the
current set does not carry therefore evaluates to nothing instead of taking
the tick down — which is the second of the three ways a parameter can be
missing, two sections on.
A set is a contract, and the caller signs it
ContextKeySet holds two sets: ContextKeySet.required and
ContextKeySet.allowed, the second the union of required and optional, and
its ContextKeySet.Builder refuses to make one key both in either order.
LootContextParamSets registers thirty-one of them into the built-in
registry BuiltInRegistries.CONTEXT_KEY_SET, which is what
LootContextParamSets.CODEC reads when a data pack names a set by id — and
the ids do not always match the field names:
LootContextParamSets.PIGLIN_BARTER is barter and
LootContextParamSets.ALL_PARAMS is generic. Every set’s keys are
tabulated in Loot context parameter
sets.
LootContextParamSets.ALL_PARAMS deserves its own warning, because it is
the default a loot table with no declared type gets
(LootTable.DEFAULT_PARAM_SET) and the set every standalone predicate, item
modifier, slot source and number provider file is validated against — and it
is all of them. It requires all sixteen keys, and
LootContextParamSets.validate fails the bootstrap if any other set allows a
key it does not require. The practical consequence: a standalone predicate
file is never flagged at load for a key it reads, so one that asks about a
block state or a damage source loads clean even though
LootContextParamSets.COMMAND — the set /execute if predicate builds —
carries neither.
Three ways a parameter can be missing
A mismatch between what a call site has and what an element wants is caught
at three different moments, with three different costs, and the three are not
alternatives — an element can pass the load-time check and still come back empty at read time. ContextMap.Builder.buildAndValidate is where the contract is enforced,
and it is the only place: it throws if the values collected include a key the set
does not allow, and again if the set requires a key that is absent. Note
what it compares — the keys the caller supplied against the set the
caller named. Nothing on this path ever sees the loot table.
- At build time.
ContextMap.Builder.buildAndValidatethrows, naming the offending keys. That is a programming error rather than a data one, and on a tick it takes the tick down with it (a command catches it and fails). - At read time.
LootContext.getOptionalis the one way a context hands out a parameter’s value (LootContext.hasParameteronly asks whether one is there), and it returns nothing for a missing key, so the conditions and functions degrade quietly rather than fail —LocationCheck,MatchBlockandEnchantmentActiveCheckall answer false. So the standalone-file validator’s silence about a block state is survivable: from/execute if predicatesuch a predicate is quietly false, not fatal. - At load time.
ValidationContext.validateContextUsagecompares what an element declares it reads —LootContextUser.getReferencedContextParams, overridden by twenty-seven classes — againstContextKeySet.allowed, and reports the difference.
The load-time check is the loose one
It checks against allowed, not
required, so an element that reads an optional key passes validation and
can still take path 2 at runtime. And what
ReloadableServerRegistries.validateLootRegistries does with the collected
problems is log them as warnings and fail the reload only on a reference
cycle: a predicate that asks for a parameter it cannot have loads fine and
misbehaves later. The same validation is a
hard error in exactly two places, both applied by a codec —
Validatable.validatorForContext for VillagerTrade, and its list form
Validatable.listValidatorForContext for every conditional effect in
EnchantmentEffectComponents. Neither a trade nor an enchantment effect can
name a predicate by id: both are decoded with the world’s registries, before
the predicate registry is loaded, so RegistryFileCodec rejects the
reference outright.
Inputs, then one invocation
LootParams is the immutable half: a ServerLevel, the ContextMap, a
map of LootParams.DynamicDrop callbacks keyed by Identifier, and a
float of luck. The ServerLevel is the mechanical guarantee that no loot context is built on the client — LootParams.Builder takes one in its
constructor and LootContext.Builder.create dereferences the server off it
for the registries, so a ClientLevel cannot produce a context at all.
LootContext is the per-invocation half, and it adds three things
LootParams does not have: the chosen RandomSource, a
HolderGetter.Provider that resolves references to other loaded elements,
and a set of LootContext.VisitedEntry used as a recursion guard.
LootContext.pushVisitedElement returns false when the element is already
present, which is how LootTable.getRandomItemsRaw refuses a table already
rolling — it logs an infinite loop and rolls nothing. The guard is a stack, not a history: LootContext.popVisitedElement takes the entry off again on
the way out, so rolling the same table twice in one evaluation is fine and
only genuine re-entrancy trips it. Both of the command call sites below —
/execute if predicate and the selector’s predicate option — seed it with
the top-level predicate before testing, but no predicate reads it back: one
that references itself by name never loads, because Validatable.validateHolder
reports the cycle as a fatal ValidationContext.RecursiveElementReferenceProblem.
Named random sequences belong to the context, not to the table.
LootContext.Builder.create takes an optional Identifier and picks a
random source three ways, in order: an explicit source or a non-zero seed
handed to LootContext.Builder.withOptionalRandomSeed, else
MinecraftServer.getRandomSequence for the named sequence, else
Level.getRandom. RandomSequences is a SavedData (level data and
rules) that derives each sequence’s
seed from the world seed, a salt and the sequence id, which is what makes a
named sequence reproducible across restarts. A loot table supplies that
identifier from its own field — and so does TradeSet.randomSequence, read
by AbstractVillager.addOffersFromTradeSet, which is why a villager’s trade
selection is reproducible by the same mechanism and has nothing to do with
loot. What a zero seed in that order means for a chest belongs to loot
tables.
What reads a context
LootContextUser — what did you read out of the map — is extended by five
data-driven types: LootItemCondition, LootItemFunction, NbtProvider,
ScoreboardNameProvider and SlotSource. The number providers read the same
map through two interfaces of their own, ContextFloatProvider and
ContextIntProvider. Each of the seven has its own registry of kinds
(data-driven types),
and three of them carry the traffic. LootItemCondition is a predicate over a
LootContext with twenty types registered in LootItemConditionTypes, from
LootItemEntityPropertyCondition and LocationCheck to
EnchantmentActiveCheck and MatchBlock; ContextFloatProvider returns a
float and has twenty-eight, from ConstantValue and UniformGenerator to
EnchantmentLevelProvider, and ContextIntProvider returns an int and has
twenty-three. Their codecs are forgiving in the same shape:
LootItemCondition.CODEC, ContextFloatProviders.CODEC and
ContextIntProviders.CODEC each take a bare id as a reference to an element
of their own registry, and the two number codecs take a bare number as a
ConstantValue. NbtProvider and ScoreboardNameProvider are the two small ones — a tag out
of an entity, a block entity or command storage, and a scoreboard name, fixed or out of a context target — and LootItemFunction belongs to loot
tables. One of the seven lives outside the loot
package altogether: SlotSource, in net/minecraft/world/item/slot, answers
a SlotCollection rather than a boolean or a number, and its six registered
kinds compose — GroupSlotSource, FilteredSlotSource, RangeSlotSource,
LimitSlotSource, ContentsSlotSource and EmptySlotSource. Its consumers
are the SlotLoot entry, which is how a loot table names a slot of the entity
it is rolling for, and SlotSourceArgument, the slots argument of /item
and /execute if items.
The advancement system tests conditions directly: the player half of every trigger but ImpossibleTrigger,
SimpleCriterionTrigger.SimpleInstance.player, is one
LootItemCondition in a Holder, named by id or written inline, and
EntityPredicate.wrap folds an EntityPredicate into a
LootItemEntityPropertyCondition to make one. The rest of
net/minecraft/advancements/predicates need not touch a context: a trigger
instance can hold an ItemPredicate or a LocationPredicate outright and
test it with no context at all, which ConsumeItemTrigger and
DistanceTrigger both do.
Who asks, and with which set
Twenty-eight callers. The six in bold ask a question with no loot table anywhere in sight, and six more roll no table either; read the six and skim the rest, which are here as the evidence for the count at the end of the section.
| caller | set | when |
|---|---|---|
BlockBehaviour.getDrops | LootContextParamSets.BLOCK | any block break (block breaking) |
Block.dropFromBlockInteractLootTable | LootContextParamSets.BLOCK_INTERACT | beehives, cave vines, carving a pumpkin, sweet berries |
LivingEntity.dropFromLootTable | LootContextParamSets.ENTITY | death (damage and death) |
LivingEntity.dropFromShearingLootTable | LootContextParamSets.SHEARING | sheep, mooshrooms, snow golems, bogged |
LivingEntity.dropFromGiftLootTable | LootContextParamSets.GIFT | hero gifts, cat gifts, chicken eggs, sniffer digs |
LivingEntity.dropFromEntityInteractLootTable | LootContextParamSets.ENTITY_INTERACT | brushing an armadillo |
RandomizableContainer.unpackLootTable | LootContextParamSets.CHEST | first touch of a structure container |
ContainerEntity.unpackChestVehicleLootTable | LootContextParamSets.CHEST | first touch of a chest minecart or chest boat |
FishingHook.retrieve | LootContextParamSets.FISHING | reeling in — luck is the hook’s plus the owner’s |
BrushableBlockEntity | LootContextParamSets.ARCHAEOLOGY | brushing suspicious sand or gravel |
PiglinAi.getBarterResponseItems | LootContextParamSets.PIGLIN_BARTER | bartering |
Mob.createEquipmentParams | LootContextParamSets.EQUIPMENT | the EquipmentUser.equip path, for a spawner’s mob |
VaultBlockEntity | LootContextParamSets.VAULT | a trial chamber vault’s display item and its reward |
TrialSpawner.ejectReward, TrialSpawnerStateData.getDispensingItems | LootContextParamSets.EMPTY | a trial spawner’s reward and its dispensed items |
AdvancementRewards.grant | LootContextParamSets.ADVANCEMENT_REWARD | advancement loot |
Enchantment.damageContext and its four siblings | the four enchanted sets plus LootContextParamSets.HIT_BLOCK | an enchantment effect’s condition |
BaseContainerBlockEntity.getLootContext | LootContextParamSets.CONTAINER_PROCESS | a furnace’s or brewing stand’s fuel |
ComposterBlock.addLayer | LootContextParamSets.BLOCK_INTERACT | how many layers an item adds to a composter |
LootCommand | block, entity, chest or fishing | /loot |
ItemCommands.applyModifier | LootContextParamSets.COMMAND | /item … modify and the from … modifier forms |
ItemCommands.getSlotsFromProvider | LootContextParamSets.COMMAND_SLOT_SOURCE | the slots of /item and /execute if items |
LootContextSources | the three command_compute sets | /compute and /data modify … compute |
ExecuteCommand | LootContextParamSets.COMMAND | /execute if predicate |
EntitySelectorOptions | LootContextParamSets.SELECTOR | a selector’s predicate argument |
EntityPredicate.createContext | LootContextParamSets.ADVANCEMENT_ENTITY | every advancement trigger that tests an entity |
ItemUsedOnLocationTrigger, AnyBlockInteractionTrigger | LootContextParamSets.ADVANCEMENT_LOCATION | placing or using an item on a block |
DefaultBlockInteractionTrigger | LootContextParamSets.BLOCK_USE | right-clicking a block, for an advancement |
AbstractVillager.addOffersFromTradeSet | LootContextParamSets.VILLAGER_TRADE | rolling and filtering a villager’s offers |
Fourteen roll a table; seventeen never do
The bold rows have no loot table anywhere in sight: a boolean, or in the
villager’s case a MerchantOffers, is the whole output.
ValidationContextSource even keeps a cached
LootContextParamSets.ADVANCEMENT_ENTITY context around, because so much
of the advancement tree validates against it.
Count sets rather than rows and the picture is starker. Fourteen of the
thirty-one are named above by a caller that goes on to roll a LootTable.
The other seventeen never do. Six are the bold rows —
LootContextParamSets.COMMAND counts among them, because its other caller,
/item’s modifier forms, applies an item modifier rather than a table. Five are the
enchantment sets, built only by Enchantment to decide whether an effect
fires. Five more are built for a number or a set of slots: the three
command_compute sets, LootContextParamSets.COMMAND_SLOT_SOURCE and
LootContextParamSets.CONTAINER_PROCESS. The seventeenth is
LootContextParamSets.ALL_PARAMS, which is never used to build a
ContextMap at all: it exists solely as a validation
context and as LootTable.DEFAULT_PARAM_SET. That is why this page is not
called loot tables.
Four facts about a question with two keys in it
/execute if predicate is a short complete use of the machine: one
command, one ContextMap, one predicate, and no loot table anywhere in it.
sequenceDiagram
participant ExecC as ExecuteCommand
participant LootP as LootParams
participant CMap as ContextMap
participant LootC as LootContext
participant LIC as LootItemCondition
Note over ExecC: the argument already holds a Holder, resolved at parse time
ExecC->>LootP: LootParams.Builder on the level, ORIGIN required, THIS_ENTITY optional
LootP->>CMap: ContextMap.Builder.<br/>buildAndValidate against LootContextParamSets.COMMAND
CMap-->>LootP: the checked map, or a throw on a bad key
ExecC->>LootC: LootContext.Builder.create with no random sequence
Note over LootC: the random source is Level.getRandom, the resolver is the reloadable registries
ExecC->>LootC: pushVisitedElement, seeding the recursion guard with this predicate
ExecC->>LIC: test
LIC->>LootC: getOptional, the one way to a parameter, null for a missing key
LIC-->>ExecC: a boolean, and the branch is taken or not
The whole machine, once, with no loot in it. Read the two dashed returns — the first is the only place the contract is enforced, and the last is the only thing the caller gets back.
Three details in that picture are worth pulling out, and a fourth about the call site it does not draw.
The predicate is resolved before the command runs. The argument type is
ResourceOrIdArgument.LootPredicateArgument, which accepts either a
registry id or an inline SNBT object and hands back a Holder — a
reference in the first case, a direct holder over a freshly parsed
condition in the second. An unknown id fails at parse time, as a command
syntax error, not at execution.
LootContextParamSets.COMMAND is thin. It requires
LootContextParams.ORIGIN and optionally allows
LootContextParams.THIS_ENTITY, and that is all: a predicate run from
/execute cannot see a block state, a damage source or a tool, whatever
the standalone-file validator let through. The selector’s version,
LootContextParamSets.SELECTOR, differs in exactly one way — it makes the
entity required, because a selector always has one.
There is no random sequence. ExecuteCommand passes an empty optional,
so Level.getRandom is what a LootItemRandomChanceCondition inside the
predicate draws from — not correlated between runs, not reproducible across
restarts.
The selector path is the same code, and it is not in the diagram.
EntitySelectorOptions builds its context per candidate entity, inside
the predicate it hands the parser, and looks the condition up itself
through the reloadable registries rather than through the argument type —
so a missing predicate there is a silent false, not a syntax error.
Six registries, rebuilt on /reload, and none of them networkable
LootDataType is the six loot registries expressed as data:
LootDataType.TABLE over Registries.LOOT_TABLE,
LootDataType.PREDICATE over Registries.PREDICATE,
LootDataType.MODIFIER over Registries.ITEM_MODIFIER,
LootDataType.SLOT_SOURCE over Registries.SLOT_SOURCE, and
LootDataType.FLOAT_PROVIDER and LootDataType.INT_PROVIDER over
Registries.CONTEXT_FLOAT_PROVIDER and Registries.CONTEXT_INT_PROVIDER.
Each pairs a registry key with a LootDataType.ContextGetter — the function
that says which ContextKeySet an element of that type is validated
against. Every type but tables gets the constant
LootContextParamSets.ALL_PARAMS; tables get LootTable.getParamSet, and that
context getter is its only caller in the game. That is the sense in which a
table’s declared type is never checked at runtime.
Reloadable, and never networkable
All six live in RegistryLayer.RELOADABLE and are rebuilt by every reload
(the resource
system),
which is what makes them the reachable half of the data-driven game: a
predicate a pack changed is live after /reload. One step of that rebuild matters here — a predicate names its tags against the
ones this reload has just read, so a tag a pack added in the same reload
resolves, though what it holds is bound only when the reload is applied. None
of the six appears in RegistryDataLoader.SYNCHRONIZED_REGISTRIES, the
list RegistrySynchronization.isNetworkable tests, so none is ever packed
for a client. What crosses the wire is only the result — a container
packet, an item entity, a command’s success.
Both dependants outside this part are Part XIII’s: its commands — this page
owns /execute if predicate itself, and what Part XIII owns is the engine it
runs in and the selector
argument whose predicate option is the
other caller — and the advancement
system, whose triggers build a context per tested entity or interaction.
For a 1.21-era reader. LootContextParam is
ContextKeyand LootContextParamSet isContextKeySet, both moved out of the loot package tonet/minecraft/util/context. The predicate library that consumes them has left the misspelled critereon package fornet/minecraft/advancements/predicatesandnet/minecraft/advancements/triggers. NumberProvider is nowContextFloatProviderandContextIntProvider, and ConditionReference and ContextAwarePredicate are gone: aHolderdoes their work. The drift table has the rest.
Where to look
Three files, in this order, are the whole argument: ContextKey,
ContextKeySet and ContextMap, in net/minecraft/util/context. They are
short, they import nothing from the loot package, and reading them is the
fastest way to see that the loot system’s foundation is not about loot.
Then LootContextParams and LootContextParamSets for the sixteen loot keys
and the thirty-one sets — the second is one long list of builders and repays
skimming rather than reading. LootParams and LootContext above them are
the two halves of one invocation, and LootContext.Builder.create is where
the random source is chosen three ways.
LootItemCondition is the interface with twenty implementations; open two of
them, LocationCheck for the ordinary shape and InvertedLootItemCondition
for one that holds another in a Holder, and then Validatable.validateHolder
for the load-time cycle check in use. ValidationContext.validateContextUsage is the
load-time check, and LootDataType is where it is told which set to check
against.
For the callers, ExecuteCommand and EntitySelectorOptions are two small complete examples of building a context by hand.
Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes tools/verify_names.py.