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

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

classwhat it decidesthread
Enchantmentthe record — a description, a definition, an exclusive set and a map of effects. No behaviour of its ownbuilt at data-pack load, read on both sides
Enchantment.EnchantmentDefinitionwhat it can go on, what it costs, which slots it counts indata-pack load
EnchantmentEffectComponentsthe thirty-one keys, one per moment an effect can actstatic, both sides
EnchantmentHelperthe static hook surface — walks stacks and slots, calls into the record, holds no stateServer for every entity and location effect; some entry points, two value effects among them, on the Render thread
ConditionalEffect / TargetedConditionalEffectwhether this effect fires here, and on whomServer
EnchantmentEntityEffectthe thing that finally happensServer only — the signature demands a ServerLevel
ItemEnchantmentsthe id-to-level map on the stack, under one of two componentsboth sides
EnchantedItemInUsethe 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 emptyServer

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.

familya representative hook or twowho makes it real
damage and protectionEnchantmentHelper.modifyDamage, EnchantmentHelper.getDamageProtectionServerPlayer.getEnchantedDamage, LivingEntity.getDamageAfterMagicAbsorb
post-attack effectsEnchantmentHelper.doPostAttackEffectsWithItemSource, EnchantmentHelper.doPostPiercingAttackEffectsPlayer.itemAttackInteraction, LivingEntity.postPiercingAttack
durability and dropsEnchantmentHelper.processDurabilityChange, EnchantmentHelper.processEquipmentDropChanceItemStack.hurtAndBreak, Mob.dropCustomDeathLoot
projectiles and the weapon in handEnchantmentHelper.processProjectileCount, EnchantmentHelper.getPiercingCountProjectileWeaponItem.draw, the AbstractArrow constructor
location and tick effectsEnchantmentHelper.runLocationChangedEffects, EnchantmentHelper.tickEffectsLivingEntity.onChangedBlock, LivingEntity.baseTick
experience and repairEnchantmentHelper.processBlockExperience, EnchantmentHelper.modifyDurabilityToRepairFromXpBlock.tryDropExperience, ExperienceOrb.repairPlayerItems
the flag questionsEnchantmentHelper.has, EnchantmentHelper.hasTagArmorSlot.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.