Enchantments
Verified against Minecraft 26.3 · Part VII · A player hits a zombie with a Fire Aspect sword, and everything that makes the zombie burn is data.
You swing a Fire Aspect sword at a zombie and the zombie catches fire. Go
looking for the class that does that — the one with the on hit, set them
alight method — and there is nothing to find. There are no enchantment
subclasses. The forty-three vanilla enchantments are JSON files in the
built-in data pack, Enchantments is a bag of forty-three registry keys that only five files outside the data generator ever name, and the whole
Fire Aspect is melee only rule is one loot condition, sitting on one
entry of a DataComponentMap that no item ever holds.
That is the pattern the rest of the page is about. An enchantment is a named modifier that holds no code; every behaviour in the game an enchantment can change is something another system asks about at a well-defined moment: one of its component keys, a tag that names it, or its id in loot data.
The cast
| class | what it decides | thread |
|---|---|---|
Enchantment | the record — a description, a definition, an exclusive set and a map of effects. No behaviour of its own | built at data-pack load, read on both sides |
Enchantment.EnchantmentDefinition | what it can go on, what it costs, which slots it counts in | data-pack load |
EnchantmentEffectComponents | the thirty-one keys, one per moment an effect can act | static, both sides |
EnchantmentHelper | the static hook surface — walks stacks and slots, calls into the record, holds no state | Server for every entity and location effect; some entry points, two value effects among them, on the Render thread |
ConditionalEffect / TargetedConditionalEffect | whether this effect fires here, and on whom | Server |
EnchantmentEntityEffect | the thing that finally happens | Server only — the signature demands a ServerLevel |
ItemEnchantments | the id-to-level map on the stack, under one of two components | both sides |
EnchantedItemInUse | the stack, the slot it was found in, its owner and a break callback — the bundle an entity or location effect receives, with a slot that may be empty | Server |
A record with a definition and a bag of components
Enchantment is a record of four things: a description Component, an
Enchantment.EnchantmentDefinition, a HolderSet of enchantments it is
exclusive with, and a DataComponentMap of effects. Enchantment.getEffects
looks a component type up in that map and returns an empty list if it is
absent, which is the whole dispatch mechanism.
The definition is where the enchanting rules live. It carries the supported
items and a narrower, optional set of primary items — both normally item
tags — so Enchantment.isPrimaryItem falls back to the supported set when
the second is absent, while Enchantment.isSupportedItem only ever asks the
first. It carries the weight, the maximum level, two Enchantment.Cost
curves (a base plus a per-level-above-first increment, read by
Enchantment.Cost.calculate), the anvil cost, and a list of
EquipmentSlotGroup — the slots in which this enchantment counts at all,
tested by Enchantment.matchingSlot. The codec bounds the weight to 1–1024
and the maximum level to 1–255, which is Enchantment.MAX_LEVEL;
Enchantment.getMinLevel is the one number that is not data, hardcoded to 1.
What a stack carries is not that record but ItemEnchantments, an
immutable id-to-level map under DataComponents.ENCHANTMENTS — or, for an
enchanted book’s inert set, under DataComponents.STORED_ENCHANTMENTS. It
is changed only through ItemEnchantments.Mutable, whose
ItemEnchantments.Mutable.upgrade merges by maximum and clamps at 255.
EnchantmentInstance, a holder-and-level pair with a
EnchantmentInstance.weight delegate, is what the weighted selection path
is built on — and that path, along with the table, the anvil, the
grindstone, the providers and /enchant, is a separate machine with its own
arithmetic, on enchanting.
Thirty-one keys, three registries and one curve
EnchantmentEffectComponents registers thirty-one component types into
BuiltInRegistries.ENCHANTMENT_EFFECT_COMPONENT_TYPE. Twenty-four hold a
list of ConditionalEffect (or TargetedConditionalEffect); the other
seven are plain — EnchantmentEffectComponents.ATTRIBUTES, two sound
lists, two unconditional values, and the two Unit-valued flags
EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP and
EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE, true by being present.
Three registries supply the effect objects, and two of the three overlap
almost entirely — which is the only thing about them worth remembering.
EnchantmentValueEffect, the odd one out, has six shapes and modifies a
running number
(AddValue, MultiplyValue, SetValue, RemoveBinomial,
ScaleExponentially and AllOf.ValueEffects). EnchantmentEntityEffect
has fifteen and does something to an entity — Ignite, DamageEntity,
ApplyMobEffect, SummonEntityEffect, AllOf.EntityEffects and ten more.
EnchantmentEntityEffect extends EnchantmentLocationBasedEffect, whose
registry has sixteen entries: those same fifteen ids plus one. That extra one
is attribute, EnchantmentAttributeEffect, which installs an AttributeModifier
(attributes) and is,
beside that registry’s own all_of (AllOf.LocationBasedEffects), the only effect
that is location-based without also being an entity effect.
The curve that scales a level
LevelBasedValue turns a level into a number, and six shapes are in its
dispatch registry (LevelBasedValue.Linear, LevelBasedValue.Clamped,
LevelBasedValue.Fraction, LevelBasedValue.LevelsSquared,
LevelBasedValue.Exponent, LevelBasedValue.Lookup).
LevelBasedValue.Constant is deliberately not among them: it is the
other arm of an either-codec, which is what makes a bare float legal
anywhere a curve is expected.
And the condition twenty-four keys sit behind
ConditionalEffect is an effect plus an optional LootItemCondition.
TargetedConditionalEffect adds two EnchantmentTarget fields — which side
of the fight the enchantment lives on (TargetedConditionalEffect.enchanted)
and which side it lands on (TargetedConditionalEffect.affected) —
except for the equipment-drops variant, whose codec reads only the first and
pins the second to EnchantmentTarget.VICTIM.
Both implement Validatable, and the effect codecs are one of only two places
in the game where a context mismatch is a hard error at decode time rather
than a logged warning. The other is VillagerTrade, and nothing else in the
game gets the treatment (contexts and
predicates
owns the three ways a parameter goes missing and the two that are fatal).
The consequence here is worth the trip: a post_attack effect asking about a
block state fails to load, rather than failing quietly at runtime a thousand
hits later.
Seven families of moment
Everything above does nothing until something calls EnchantmentHelper. The enchantment package is called from all over the game, so the artefact worth keeping is a table of who calls what: every entry point with
its callers is the enchantment hook
table, and these are the seven kinds of moment its effect hooks fall into; the rest of the table stores, looks up or chooses enchantments.
| family | a representative hook or two | who makes it real |
|---|---|---|
| damage and protection | EnchantmentHelper.modifyDamage, EnchantmentHelper.getDamageProtection | ServerPlayer.getEnchantedDamage, LivingEntity.getDamageAfterMagicAbsorb |
| post-attack effects | EnchantmentHelper.doPostAttackEffectsWithItemSource, EnchantmentHelper.doPostPiercingAttackEffects | Player.itemAttackInteraction, LivingEntity.postPiercingAttack |
| durability and drops | EnchantmentHelper.processDurabilityChange, EnchantmentHelper.processEquipmentDropChance | ItemStack.hurtAndBreak, Mob.dropCustomDeathLoot |
| projectiles and the weapon in hand | EnchantmentHelper.processProjectileCount, EnchantmentHelper.getPiercingCount | ProjectileWeaponItem.draw, the AbstractArrow constructor |
| location and tick effects | EnchantmentHelper.runLocationChangedEffects, EnchantmentHelper.tickEffects | LivingEntity.onChangedBlock, LivingEntity.baseTick |
| experience and repair | EnchantmentHelper.processBlockExperience, EnchantmentHelper.modifyDurabilityToRepairFromXp | Block.tryDropExperience, ExperienceOrb.repairPlayerItems |
| the flag questions | EnchantmentHelper.has, EnchantmentHelper.hasTag | ArmorSlot.mayPickup for Curse of Binding, four blocks for the four prevents tags |
The location row is the one that keeps state:
LivingEntity.activeLocationDependentEnchantments remembers which
location-based effects are running in which slot, so the system can tell
became active from still active, and so an attribute effect can be taken
off cleanly by EnchantmentHelper.stopLocationBasedEffects when the armour
comes off.
How one hook fires
Most hooks in those seven rows are the same shape underneath. A system reaches a
moment and asks EnchantmentHelper; the helper picks which stacks to walk,
filtering by slot if it was given one; the record picks which effect entries
apply; a loot condition decides whether this one fires. Only then does
anything happen. The flag row is the exception and is the reason the shape is
worth drawing: EnchantmentHelper.has and EnchantmentHelper.hasTag build no context and run no
condition: the first asks the record whether the key is present at all, the second
whether the enchantment is in a tag.
flowchart TD
Caller["a system reaches a moment"] --> EH{"which EnchantmentHelper entry point"}
EH -- "EnchantmentHelper.has, EnchantmentHelper.hasTag" --> Flag["ask the record or its tags: is it there at all"]
EH -- "any of the other six" --> Walk["walk one stack, or every EquipmentSlot of one entity"]
Walk --> Slot["on the slot-aware walk, keep entries Enchantment.matchingSlot accepts"]
Slot --> Comp["read the list under one EnchantmentEffectComponents key"]
Comp --> Target["for the targeted keys, keep entries whose enchanted target matches"]
Target --> Ctx["build a LootContext on that hook's parameter set"]
Ctx --> Cond{"ConditionalEffect.matches runs the LootItemCondition"}
Cond -- "no" --> Drop["nothing happens"]
Cond -- "yes" --> Apply["fold a value, or run the effect on the affected entity"]
One shape, and the flag row’s way round it. The flag row answers from the record and its tags alone, with no context built and no condition run.
The LootContext in the middle is the same machinery loot tables and
/execute if predicate use, and Enchantment builds five of them —
Enchantment.damageContext plus four private siblings for items, entities,
locations and block hits, one per parameter set the components name.
Contexts and predicates
owns that half.
Fire Aspect, from the click to the flame
Fire Aspect as data is one small JSON file: supported and primary items
are tags, weight 2, maximum level 2, anvil cost 4, dynamic cost curves, one
slot (mainhand), and one effect — a
EnchantmentEffectComponents.POST_ATTACK entry whose enchanted target is
the attacker, whose affected target is the victim, whose effect is Ignite
with a linear duration of four seconds per level, behind one damage-source
predicate requiring a direct hit.
sequenceDiagram
participant SGPL as ServerGamePacket<br/>ListenerImpl
participant Player as Player
participant EH as EnchantmentHelper
participant Ench as Enchantment
participant Ignite as Ignite
participant Entity as Entity
SGPL->>Player: attack, once handleAttack has passed the range checks
Player->>Player: createAttackSource, whose direct and causing entity are one
Player->>EH: doPostAttackEffects<br/>WithItemSource,<br/>on a hit that landed
EH->>EH: the victim's whole equipment first, then the attacker's main hand
EH->>Ench: doPostAttack for the main-hand stack, in the ATTACKER pass
Ench->>Ench: TargetedConditional<br/>Effect.matches asks <br/>if the hit was direct
Ench->>Ignite: apply, with the victim as the affected target
Ignite->>Entity: igniteForSeconds, raised only if the new value is larger
rect rgba(0, 0, 0, 0.04)
Note over Ignite,Entity: a later tick, and every tick after it while the fire lasts
Entity->>Entity: baseTick sets the shared flags byte through SynchedEntityData
Note over Entity: the flame travels as a data packet, the enchantment never does
end
Six lanes between the click and the flame. The band is where the enchantment has already finished —
what the other player sees is a SynchedEntityData flag, and one point of damage a
second is the entity’s own business.
The swing, and the source that is the whole melee rule.
ServerGamePacketListenerImpl.handleAttack runs the range checks and calls
Player.attack — unless the main-hand item has
DataComponents.PIERCING_WEAPON, in which case the packet is dropped and
none of this happens (see the spear
for its own path). Player.createAttackSource then asks
ItemStack.getDamageSource, and every branch of it reaches the
single-entity DamageSource constructor — the one that sets the direct
entity and the causing entity to the same object, making
DamageSource.isDirect true. An arrow’s source has an arrow as the direct
entity and a player as the causing entity, so it is false. That one
comparison, read by one loot condition in one JSON file, is why Fire Aspect
never fires through a bow.
The damage, then the hook. ServerPlayer.getEnchantedDamage — the
override, not Player’s base version, which returns its argument unchanged
— folds Sharpness and friends in through EnchantmentHelper.modifyDamage,
and the hit goes through Entity.hurtOrSimulate to LivingEntity.hurtServer
(damage and death). Only if that returned
true does Player.itemAttackInteraction call
EnchantmentHelper.doPostAttackEffectsWithItemSource.
The one method has three branches. The helper walks the victim’s whole
equipment first, with EnchantmentTarget.VICTIM as the pass — that is
Thorns’ lane, and why Thorns works from a chestplate while Fire Aspect does
not work from boots. Then, for a living causing entity, it walks the one weapon stack it was
handed, in the EnchantmentTarget.ATTACKER pass, keeping enchantments whose
declared slots include the main hand. Its third branch
takes a causing entity that is not living: a slotless walk with no filter at
all, which only fires when a break callback was supplied, and whose only
vanilla caller is ThrownTrident. An attacker’s armour can never
contribute a post-attack effect.
That second branch is worth one more sentence, because the slot it names is a
label rather than a fact. It hands EquipmentSlot.MAINHAND to the filter
whatever the weapon actually is, and so does
EnchantmentHelper.doPostPiercingAttackEffects; KineticWeapon.damageEntities
reaches Player.stabAttack (a mob’s LivingEntity.stabAttack) with whichever
slot the use was in. An
off-hand spear’s enchantments are therefore tested against the main-hand slot
group. The slot question itself is asked two ways: the two
EnchantmentHelper.forEachModifier overloads test different things — the
EquipmentSlot one asks Enchantment.matchingSlot, the EquipmentSlotGroup
one asks whether the definition declares that exact group — and
ItemStack.forEachModifier uses one of each. One more pair like it is worth
knowing before you grep: the instance method is spelled
Enchantment.modifyArmorEffectivness, Mojang’s typo, while the helper beside
it is EnchantmentHelper.modifyArmorEffectiveness.
The condition and the target. Enchantment.doPostAttack keeps the
entries whose enchanted target matches the pass, builds the
enchanted-damage context — victim, level, origin, damage source, and both
attacking entities as optional parameters — and runs the predicate. The
affected field then picks who receives the effect: attacker, direct
entity, or victim. Fire Aspect says victim, and if that works out to null
the effect is dropped in silence.
The burn. Ignite.apply calls Entity.igniteForSeconds, which floors
to ticks and hands Entity.igniteForTicks a number it applies only if it
is larger than the counter already there — while clearing any freeze
regardless, so re-hitting a burning target with a weaker Fire Aspect does
nothing except thaw it. No damage happens here. Entity.baseTick deals it,
one point every twentieth tick, on a ServerLevel, skipped in lava and
replaced by Entity.clearFire for a fire-immune entity. The same method
sets shared flag zero through Entity.setSharedFlagOnFire
(synched entity
data),
which becomes
Entity.displayFireAnimation on the client — whose own Entity.baseTick,
finding no ServerLevel, clears the fire counter instead of burning.
Three famous ones driven from the other end
The three enchantments players talk about most are driven from the other end: something else finds each one on the stack and does the famous work, and only one of the three has no effect component at all.
Fortune lives in the loot table. ApplyBonusCount and
BonusLevelTableCondition read the tool parameter out of a LootContext
and call EnchantmentHelper.getItemEnchantmentLevel on it — a level, not an
effect. Looting is the same trick twice more:
EnchantedCountIncreaseFunction and
LootItemRandomChanceWithEnchantedBonusCondition both read the attacking
entity parameter and call EnchantmentHelper.getEnchantmentLevel, which walks the slots of a LivingEntity the enchantment counts in and keeps the
best. Fortune has no effect component whatsoever, and Looting’s only one is an
equipment_drops entry, which raises the chance a mob drops its equipment and
plays no part in its loot table
(loot tables).
Mending inverts it once more: ExperienceOrb.repairPlayerItems asks
EnchantmentHelper.getRandomItemWith for a damaged stack carrying
EnchantmentEffectComponents.REPAIR_WITH_XP, so the orb drives the repair
and the item is found. What runs on the item is one number: its
EnchantmentEffectComponents.REPAIR_WITH_XP entry, a multiply by two that
EnchantmentHelper.modifyDurabilityToRepairFromXp applies, makes each point of
experience buy two points of durability.
What runs on the client, and why no entity effect does
No entity or location effect can: EnchantmentEntityEffect and
EnchantmentLocationBasedEffect both demand a ServerLevel in their
signatures, which is the same mechanical guarantee the loot system uses. But
two values do. Enchantment.modifyUnfilteredValue takes only a
RandomSource, and its two users are Enchantment.modifyCrossbowChargeTime
and Enchantment.modifyTridentSpinAttackStrength.
Both are visible as gameplay. CrossbowItem.getChargeDuration is called by
three entity renderers, by FirstPersonHandsAndItems and by the CrossbowPull item
property, so Quick Charge is evaluated on the Render thread every frame a
crossbow is being drawn — an enchantment hook in the frame loop, to pose an arm and pick one of three
textures. And MultiPlayerGameMode.releaseUsingItem runs
TridentItem.releaseUsing on the client’s own copy, which asks
EnchantmentHelper.getTridentSpinAttackStrength, so Riptide’s strength is
computed client-side too, which is what lets the riptide push be predicted
at all (using an item).
The rest is drawing, or a question with no effect behind it. The drawing is
ItemEnchantments.addToTooltip for the tooltip, ItemStack.hasFoil for the
glint, and EnchantmentHelper.forEachModifier for the attribute lines, which
means the client evaluates the LevelBasedValue curve itself. The questions are Curse of Binding’s flag, which
ArmorSlot.mayPickup and Equippable.swapWithEquipmentSlot ask while a click or
a use is predicted, and Riptide’s sound, which
EnchantmentHelper.pickHighestLevel chooses before the ServerLevel test. And
the anvil and the grindstone build their results on the client’s copy of the menu
too, with the same helpers the server uses. That is what the cast’s some entry
points on the Render thread covers.
Where the forty-three live, and when they are read
At world load, and not again. Registries.ENCHANTMENT and
Registries.ENCHANTMENT_PROVIDER are both in
RegistryDataLoader.WORLD_REGISTRIES, the dynamic registries built once
when a world opens (identifiers and
registries) —
not in RegistryLayer.RELOADABLE, where the loot tables and predicates live.
/reload rebuilds those and every recipe and leaves the enchantments themselves
as the world found them, their tags aside, which it re-reads with every other
registry’s; changing an enchantment takes a restart. That is the sharpest
difference between this part’s three engines, and the one a data-pack author
meets first.
What crosses the wire is usually an id and nothing else.
Registries.ENCHANTMENT is in RegistryDataLoader.SYNCHRONIZED_REGISTRIES
with the full Enchantment.DIRECT_CODEC, but
RegistrySynchronization.packRegistry sends a bare Identifier for every
element whose pack the client already has — for a vanilla client against a
vanilla server, all forty-three. Full definitions cross only for a data pack’s
custom or overridden enchantments. Beyond that,
ClientboundUpdateTagsPacket for EnchantmentTags and, per stack, the
DataComponents.ENCHANTMENTS component: registry ids and levels.
One JSON file, four effect objects, no Java
Enchantments.LUNGE is the only user of
EnchantmentEffectComponents.POST_PIERCING_ATTACK in the game, and it is the
whole pattern in one file. Its single effect is an AllOf.EntityEffects of
four — a ChangeItemDamage, an ApplyExhaustion scaled per level, an
ApplyEntityImpulse forward with its vertical component scaled away, and a
PlaySoundEffect holding three sounds, indexed by level rather than shuffled
— behind a four-clause condition checking that the user is not riding, not
elytra-flying, not in water, and either not a player, in creative, or fed.
Four effect objects, four predicates, no Java, and a lunge that shoves you forward and tires you out. That is what an enchantment is.
Questions players ask
Why is an enchanted book inert? Because every effect hook reads DataComponents.ENCHANTMENTS and nothing else,
through the private walks (EnchantmentHelper.runIterationOnItem among them) or,
for Mending’s EnchantmentHelper.getRandomItemWith, directly. A book’s set lives
under DataComponents.STORED_ENCHANTMENTS, and the routing between the two
is keyed on the exact item Items.ENCHANTED_BOOK
(enchanting owns that routing).
The component is not so exclusive: ItemStack puts
DataComponents.STORED_ENCHANTMENTS in any stack’s tooltip, and the anvil
prices its right-hand input as a book by testing for that component rather
than for the item (enchanting)
— so a data pack can make a stick priced like one, but never make it behave
like one.
Does Fire Aspect cook the loot? Not through the enchantment. The cooking
is SmeltItemFunction, an ordinary loot function on the mob’s own table,
behind a condition that passes when the victim is on fire or the direct
attacker’s main hand carries an enchantment in EnchantmentTags.SMELTS_LOOT —
a tag whose only member is Fire Aspect (loot
tables). So the sword you swung is read twice
by two systems that know nothing about each other: once for its effect
component, and once, much later, as a name in a tag.
Where to look
Enchantment is four fields and will take ten minutes; read it first, because
everything else on this page is about how those four fields are consulted.
Enchantment.EnchantmentDefinition inside it is the enchanting rules, and
EnchantmentEffectComponents beside it is the list of thirty-one moments.
Then EnchantmentHelper, which is the entire behaviour surface and holds no
state. Read the private EnchantmentHelper.runIterationOnItem and its slot-aware
siblings before any public entry point: they are the walk, and most hooks above
them are a thin wrapper around one of them. The hook
table is the map of which callers
reach which.
For the effect side, ConditionalEffect and TargetedConditionalEffect are the two shapes a
conditional entry takes, and Ignite is the smallest effect
worth reading — three lines that set an entity on fire. LevelBasedValue is the curve underneath most of them.
For what a stack carries rather than what an enchantment is,
ItemEnchantments; for the selection machinery this page deliberately leaves
alone, EnchantmentInstance and then enchanting.
Rules: names, never code · how the system works, not how the code reads ·
newest version only · every backticked name passes tools/verify_names.py.