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

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

classpackagewhat it ownswhen
ContextKeyutil/contextone parameter’s name and its Java type, and nothing else—
ContextKeySetutil/contextwhich keys a call site must supply, and which it maybuilt once at class-init
ContextMaputil/contextthe values, and the only place the contract is enforcedServer for loot; the Render thread builds its own for a recipe display, and world generation one per NoiseChunk
LootParamsstorage/lootthe ServerLevel, the map, the dynamic-drop callbacks, the luckServer
LootContextstorage/lootone invocation: the random source, the reference resolver, the visited stackServer
LootItemConditionstorage/loot/predicatesa predicate over a LootContext — the boolean answerServer
ContextFloatProviderstorage/loot/providersa float over a LootContext — the numeric answer, beside ContextIntProvider for an intServer
ValidationContextstorage/lootat load, whether an element asked for a key its set does not havethe 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.

  1. At build time. ContextMap.Builder.buildAndValidate throws, 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).
  2. At read time. LootContext.getOptional is the one way a context hands out a parameter’s value (LootContext.hasParameter only asks whether one is there), and it returns nothing for a missing key, so the conditions and functions degrade quietly rather than fail — LocationCheck, MatchBlock and EnchantmentActiveCheck all answer false. So the standalone-file validator’s silence about a block state is survivable: from /execute if predicate such a predicate is quietly false, not fatal.
  3. At load time. ValidationContext.validateContextUsage compares what an element declares it reads — LootContextUser.getReferencedContextParams, overridden by twenty-seven classes — against ContextKeySet.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.

callersetwhen
BlockBehaviour.getDropsLootContextParamSets.BLOCKany block break (block breaking)
Block.dropFromBlockInteractLootTableLootContextParamSets.BLOCK_INTERACTbeehives, cave vines, carving a pumpkin, sweet berries
LivingEntity.dropFromLootTableLootContextParamSets.ENTITYdeath (damage and death)
LivingEntity.dropFromShearingLootTableLootContextParamSets.SHEARINGsheep, mooshrooms, snow golems, bogged
LivingEntity.dropFromGiftLootTableLootContextParamSets.GIFThero gifts, cat gifts, chicken eggs, sniffer digs
LivingEntity.dropFromEntityInteractLootTableLootContextParamSets.ENTITY_INTERACTbrushing an armadillo
RandomizableContainer.unpackLootTableLootContextParamSets.CHESTfirst touch of a structure container
ContainerEntity.unpackChestVehicleLootTableLootContextParamSets.CHESTfirst touch of a chest minecart or chest boat
FishingHook.retrieveLootContextParamSets.FISHINGreeling in — luck is the hook’s plus the owner’s
BrushableBlockEntityLootContextParamSets.ARCHAEOLOGYbrushing suspicious sand or gravel
PiglinAi.getBarterResponseItemsLootContextParamSets.PIGLIN_BARTERbartering
Mob.createEquipmentParamsLootContextParamSets.EQUIPMENTthe EquipmentUser.equip path, for a spawner’s mob
VaultBlockEntityLootContextParamSets.VAULTa trial chamber vault’s display item and its reward
TrialSpawner.ejectReward, TrialSpawnerStateData.getDispensingItemsLootContextParamSets.EMPTYa trial spawner’s reward and its dispensed items
AdvancementRewards.grantLootContextParamSets.ADVANCEMENT_REWARDadvancement loot
Enchantment.damageContext and its four siblingsthe four enchanted sets plus LootContextParamSets.HIT_BLOCKan enchantment effect’s condition
BaseContainerBlockEntity.getLootContextLootContextParamSets.CONTAINER_PROCESSa furnace’s or brewing stand’s fuel
ComposterBlock.addLayerLootContextParamSets.BLOCK_INTERACThow many layers an item adds to a composter
LootCommandblock, entity, chest or fishing/loot
ItemCommands.applyModifierLootContextParamSets.COMMAND/item … modify and the from … modifier forms
ItemCommands.getSlotsFromProviderLootContextParamSets.COMMAND_SLOT_SOURCEthe slots of /item and /execute if items
LootContextSourcesthe three command_compute sets/compute and /data modify … compute
ExecuteCommandLootContextParamSets.COMMAND/execute if predicate
EntitySelectorOptionsLootContextParamSets.SELECTORa selector’s predicate argument
EntityPredicate.createContextLootContextParamSets.ADVANCEMENT_ENTITYevery advancement trigger that tests an entity
ItemUsedOnLocationTrigger, AnyBlockInteractionTriggerLootContextParamSets.ADVANCEMENT_LOCATIONplacing or using an item on a block
DefaultBlockInteractionTriggerLootContextParamSets.BLOCK_USEright-clicking a block, for an advancement
AbstractVillager.addOffersFromTradeSetLootContextParamSets.VILLAGER_TRADErolling 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 ContextKey and LootContextParamSet is ContextKeySet, both moved out of the loot package to net/minecraft/util/context. The predicate library that consumes them has left the misspelled critereon package for net/minecraft/advancements/predicates and net/minecraft/advancements/triggers. NumberProvider is now ContextFloatProvider and ContextIntProvider, and ConditionReference and ContextAwarePredicate are gone: a Holder does 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.