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

Data components

Verified against Minecraft 26.2 · Part II · A player types /give @s diamond_sword[enchantments={sharpness:3}], and later rolls Sharpness onto a plain sword at the enchanting table: what the square brackets are, and how the client finds out.

A player types /give @s diamond_sword[enchantments={sharpness:3}]. The part in square brackets is a patch: one keyed, typed value laid over the sword. A data component is exactly that — one typed, keyed piece of data attached to an item stack: its damage, its enchantments, its lore, the food it is, the armour slot it goes in. The item type supplies a prototype map of components; a stack carries only a patch against that prototype. Everything that used to be “the NBT on an item” is a component with a codec, and most of what used to be a subclass of Item carrying behaviour (a sword, a piece of armour) is now a kit of components on a plain Item. The surprise is where the prototype comes from. It is not built in the item’s constructor: Item.Properties only records an initializer, and the map is built again on every reload with the world’s registries in hand — DataComponentInitializers.build on the reload worker, Holder.Reference.bindComponents on the owning thread. That is why a data pack can change what a jukebox plays without touching the item, and why a stack cannot even be decoded before the first reload: Item.CODEC_WITH_BOUND_COMPONENTS guards on Holder.areComponentsBound.

The cast

classwhat it decidesthread
DataComponentTypethe key: which codec writes the value to disk, which to the wire, and whether it is saved at allstatic, registered at bootstrap
DataComponentMapan immutable, identity-keyed map — the prototype’s shapeany; never mutated
PatchedDataComponentMapwhat an ItemStack actually owns: a shared prototype, a patch, and whether the patch is still someone else’swhichever thread owns the stack
DataComponentPatchthe serialisable form of the patch: additions and removalsNetty for packets; server and workers for saves
DataComponentInitializersbuilds every registry element’s prototype, from the recorded initializers, once the registries existreload worker
Holder.Referenceholds the bound prototype for one registry element, and throws until it has onebound on the server thread after a reload; on the client at the end of configuration
ItemStackthe writes (ItemStack.set, ItemStack.update, ItemStack.remove, ItemStack.copyFrom) and the reads it inherits from DataComponentHolderwhichever thread owns the stack
DataComponentLookupthe reverse index: which elements of a registry carry this component valuebuilt at freeze, populated lazily

Everything here ships in both jars; in the trace below only ClientPacketListener is client-only.

The shape of a stack

flowchart LR
    DCI["DataComponentInitializers.build, on the reload worker, with the registries in hand"] --> HR
    subgraph ITEM["Item, one per registry element"]
        HR["Holder.Reference, bindComponents at reload"] --> PROTO["the prototype: a DataComponentMap, immutable and identity-keyed"]
    end
    subgraph STACK["ItemStack"]
        PDM["PatchedDataComponentMap"] --> PATCH["patch: type to Optional value, empty meaning removed from the prototype"]
        PDM --> COW["copyOnWrite: the map is still shared with the stack it was copied from"]
    end
    PDM -. "prototype, shared by every stack of the item" .-> PROTO
    PATCH -- "asPatch and fromPatch" --> DCP["DataComponentPatch: added values and removed keys, the form on disk and on the wire"]
    KEY["DataComponentType: the key, with a persistent codec, a network codec, or only the latter"] --> PATCH
    KEY --> PROTO

Read it left to right. The item’s prototype is built by DataComponentInitializers and bound onto the item’s Holder.Reference. A stack points at that shared prototype and owns only a patch over it, whose values are Optional — an empty value is a removal. The patch’s serialisable twin is a DataComponentPatch, and the key of every entry in all of them is a DataComponentType. The rest of the page is a tour of those objects, each grounded in one small trace: Sharpness III arriving on a sword at the enchanting table.

The key: DataComponentType

A type is built by DataComponentType.Builder. DataComponentType.Builder.persistent gives the disk and JSON codec; DataComponentType.Builder.networkSynchronized a hand-written wire codec. A type with no persistent codec is transient (DataComponentType.isTransient): never saved, only sent. DataComponentType.Builder.networkSynchronized is not a gate — without it, DataComponentType.Builder.build derives a wire codec from the persistent codec (NBT over the wire), and with neither it throws. The only real switch is transient-versus-persistent, and it gates saving. Three types exist only on the wire: DataComponents.CREATIVE_SLOT_LOCK, DataComponents.ADDITIONAL_TRADE_COST, DataComponents.MAP_POST_PROCESSING.

111 — vanilla types, registered in DataComponents into BuiltInRegistries.DATA_COMPONENT_TYPE; 29 of them have slash-shaped ids (villager/variant and its siblings). The catalogue is reference/components. Component types are code, in a registry data packs cannot extend; what data packs reach is the prototype (below) and CustomData, which carries arbitrary NBT for them to use.

Two more flags live on the builder. DataComponentType.Builder.cacheEncoding routes a type’s encodes through EncoderCache (DataComponents.ENCODER_CACHE). DataComponentType.Builder.ignoreSwapAnimation is set on exactly one type, DataComponents.DAMAGE — so durability ticking down does not replay the held-item swap on the client. Underneath, DataComponentType.PERSISTENT_CODEC and DataComponentType.VALUE_MAP_CODEC are the shared dispatch machinery that DataComponentMap.CODEC and the predicates are built on.

The maps

DataComponentMap is immutable and identity-keyed; DataComponentMap.Builder builds one and can carry a DataComponentMap.Builder.addValidator, which is where the prototype-time structural rule lives (below). DataComponentMap.EMPTY is the map every registry element gets when nothing declared one. DataComponentMap.composite is dead API: it exists, and nothing in 26.2 calls it; the layering that actually happens is PatchedDataComponentMap’s prototype-plus-patch.

PatchedDataComponentMap is the map an ItemStack actually owns: a PatchedDataComponentMap.prototype shared with every stack of that item, a PatchedDataComponentMap.patch whose values are Optional (empty means removed from the prototype), and a PatchedDataComponentMap.copyOnWrite flag. It sanitises on every write: PatchedDataComponentMap.set stores nothing when the value equals the prototype’s, and PatchedDataComponentMap.remove stores a removal marker only if the prototype had the key. In the trace, the sword’s prototype carries an empty ItemEnchantments (every item’s does, through DataComponents.COMMON_ITEM_COMPONENTS), so Sharpness III differs from the prototype and the patch gains one entry. Set the enchantments back to empty and the entry vanishes rather than becoming an explicit default.

The first write pays for the copy. PatchedDataComponentMap.ensureMapOwnership clones the backing map only when PatchedDataComponentMap.copyOnWrite is set; ItemStack.copy, PatchedDataComponentMap.asPatch and ItemStack.transmuteCopy all alias the same map and set the flag, so copying a stack is O(1) until someone writes. ItemStack.applyComponentsAndValidate relies on that: it snapshots with PatchedDataComponentMap.asPatch, applies, runs ItemStack.validateStrict, and on failure PatchedDataComponentMap.restorePatch.

Equality is prototype plus patch. ItemStack.isSameItemSameComponents bottoms out in PatchedDataComponentMap.equals. Because setting a value equal to the prototype default removes it from the patch, two stacks that reached the default by different routes compare equal.

The patch, on the wire and on disk

DataComponentPatch is the serialisable form: additions and removals. DataComponentPatch.CODEC writes removals as !minecraft:foo; DataComponentPatch.STREAM_CODEC writes two counts then the entries; DataComponentPatch.DELIMITED_STREAM_CODEC is the length-prefixed variant for untrusted input; and DataComponentPatch.split is the added/removed decomposition that the hashing and block-entity paths are both built on. TypedDataComponent — a type with its value — has its own “type id then value” stream codec, distinct from the patch encoding, for the places one value travels alone.

The wire patch never contains defaults — for a real stack. Because PatchedDataComponentMap sanitises on every write, a value equal to the prototype’s is dropped rather than sent. ItemStackTemplate is the exception: it holds a raw DataComponentPatch straight from the builder, which does no such comparison, and sends it verbatim.

On the network the patch travels inside every ItemStack in ClientboundContainerSetSlotPacket, ClientboundContainerSetContentPacket, ClientboundSetCursorItemPacket, ClientboundSetPlayerInventoryPacket and ClientboundSetEquipmentPacket; the client answers ordinary clicks with hashes, not stacks, and the creative slot alone sends a full, re-validated stack — codecs, NBT and JSON owns the four serialisations of a stack, and containers and menus owns what the server does with a hash. On disk an item is saved as a patch (ItemStack.MAP_CODEC’s “components”, with transient types silently dropped), but a block entity as a full DataComponentMap — the two are not symmetric.

The prototype, and why it is built at reload

An Item constructor registers its initializer in BuiltInRegistries.DATA_COMPONENT_INITIALIZERS. Item.Properties.component and its convenience methods (Item.Properties.durability, Item.Properties.food, Item.Properties.equippable, Item.Properties.sword, …) only record a DataComponentInitializers.Initializer; nothing is a map yet. Item.Properties.delayedComponent and Item.Properties.delayedHolderComponent are the reason it must be deferred: they name registry entries that do not exist until a world’s registries do — an item’s DataComponents.JUKEBOX_PLAYABLE or DataComponents.DAMAGE_RESISTANT names an entry a data pack can change.

The maps are built with full registry context — tags, damage types, jukebox songs resolvable — by DataComponentInitializers.build on the reload worker on the server, and installed with Holder.Reference.bindComponents on the owning thread: on the server in ReloadableServerResources.updateComponentsAndStaticRegistryTags (server thread, after every reload including /reload), on the client in RegistryDataCollector at the end of configuration. A /reload therefore rebinds every item’s prototype. Every registry element gets a component map (DataComponentMap.EMPTY if it had no initializer), so EntityType holders have one too — with one asymmetry, and it runs the way round you would not guess. ClientConfigurationPacketListenerImpl passes Connection.isMemoryConnection into RegistryDataCollector.collectGameRegistries, which negates it, so a singleplayer client binds only the registries RegistrySynchronization.isNetworkable accepts and a multiplayer client binds every one. Singleplayer can skip the rest because it shares BuiltInRegistries with the integrated server, whose own apply has already bound them.

Before binding, reading a registry element’s components throws, and the failure is a null-check, not a friendly error: Item.components merely delegates, the throw comes from Holder.Reference.components, and the non-throwing question is Holder.areComponentsBound. Item.CODEC_WITH_BOUND_COMPONENTS guards on it and refuses to decode a stack until then.

Ten — entries in DataComponents.COMMON_ITEM_COMPONENTS, the map every item’s prototype starts from. Notably it puts an empty ItemEnchantments on every item, which is what ItemStack.isEnchantable depends on: it gates first on DataComponents.ENCHANTABLE being present, and then on DataComponents.ENCHANTMENTS being present and empty.

There are two structural rules, at two different times. At prototype-build time a validator installed by Item.Properties rejects an item that is both damageable and stackable. At stack time ItemStack.validateStrict rejects a DataComponents.MAX_DAMAGE alongside a DataComponents.MAX_STACK_SIZE above one, a count above the stack’s own maximum, an over-weight bundle, and contained items whose counts exceed their own limits. That last check reaches exactly one level into containers, bundles and charged projectiles and does not re-run the full validation there — nesting is not followed.

The reverse index: DataComponentLookup

Every frozen MappedRegistry builds one (Registry.componentLookup): a lazily-populated reverse index answering “which elements carry this component value?”, which is how the game finds the spawn egg for an entity type or the item for a dye colour. It reads the same bound prototypes the holders carry, so it too is meaningless before the first reload.

The readers and the predicates

DataComponentGetter reads one component; DataComponentHolder reads one and has a map, and is implemented only by ItemStack, which is where DataComponentHolder.get, DataComponentHolder.getOrDefault and DataComponentHolder.has come from. ItemInstance is the read-only face over ItemStack and ItemStackTemplate (item, count, patch — a record) that predicates and recipes take. Beyond item behaviour, the callers are loot functions (CopyComponentsFunction) and the /give and /item commands.

The predicates come in two strengths. DataComponentExactPredicate requires every listed component to equal. The partial DataComponentPredicate family under core/component/predicatesDataComponentPredicates, 15 kinds, in their own BuiltInRegistries.DATA_COMPONENT_PREDICATE_TYPE registry — matches a shape rather than a value, and DataComponentMatchers joins the two for ItemPredicate.

The values, by package

packagevalue types
world/item/componentConsumable, Tool, Weapon, BlocksAttacks, ItemLore, CustomData, TooltipDisplay, ItemContainerContents, BundleContents, TypedEntityData
world/item/equipmentEquippable
world/item/enchantmentItemEnchantments, Enchantable, Repairable

Item subclasses no longer carry combat. Weapon, BlocksAttacks, KineticWeapon, PiercingWeapon, AttackRange, SwingAnimation and Tool are components; Item.Properties.sword, Item.Properties.spear and Item.Properties.humanoidArmor build whole kits, and SwordItem is gone. But the tools that act on a block are not: AxeItem, ShovelItem and HoeItem still exist as classes, purely for stripping, path-making and tilling — their combat and mining live in components like everything else.

The trace: Sharpness at the enchanting table

sequenceDiagram
    participant EM as EnchantmentMenu
    participant IStack as ItemStack
    participant PDM as PatchedDataComponentMap
    participant ACM as AbstractContainerMenu
    participant CPL as ClientPacketListener

    Note over EM: server thread, ServerboundContainerButtonClickPacket has arrived
    EM->>IStack: transmuteCopy(Items.ENCHANTED_BOOK) if the input is a book, same patch over a new prototype
    EM->>IStack: enchant(holder, level) for each chosen EnchantmentInstance
    IStack->>IStack: EnchantmentHelper.updateEnchantments, then set of STORED_ENCHANTMENTS for a book, ENCHANTMENTS otherwise
    IStack->>PDM: set: ensureMapOwnership clones the shared map, the value differs from the prototype's empty set, so patch.put
    Note over PDM: patch is now {minecraft:enchantments to {sharpness: 3}}
    Note over ACM: still inside the packet handler, which calls broadcastChanges itself once the click is accepted
    ACM->>ACM: broadcastChanges, RemoteSlot.Synchronized.matches fails for this slot
    ACM->>CPL: ClientboundContainerSetSlotPacket: count, item id, DataComponentPatch.STREAM_CODEC
    CPL->>CPL: decode: new ItemStack(holder, count, patch), fromPatch against the client's own bound prototype
    CPL->>CPL: handleContainerSetSlot, AbstractContainerMenu.setItem, tooltip via ItemEnchantments.addToTooltip

The menu owns the mutation. EnchantmentMenu.clickMenuButton runs under ContainerLevelAccess.execute on the server thread. For a book it first calls ItemStack.transmuteCopy — a new stack with the same patch applied to the enchanted book’s prototype — then ItemStack.enchant per chosen EnchantmentInstance. ItemStack.enchant hands the edit to EnchantmentHelper.updateEnchantments, which reads the current ItemEnchantments, edits a mutable copy and writes the immutable result back with ItemStack.set; the enchanting rules, the lapis, the seed and /enchant are Part VII’s. What matters here is that enchantments are a value, not a list on the stack: one component, one write.

One write, one patch entry. ItemStack.set is PatchedDataComponentMap.set. The map the sword owned was shared with whatever it was copied from, so PatchedDataComponentMap.ensureMapOwnership clones it now; the new ItemEnchantments differs from the prototype’s empty one, so the patch gains its single entry. A book that was transmuted a moment earlier carries the same patch over a different prototype, which is the whole meaning of ItemStack.transmuteCopy.

The menu compares, and here it does not wait for the tick. AbstractContainerMenu.broadcastChanges compares every slot against what the client was last told (RemoteSlot.Synchronized). It runs from ServerPlayer.tick in the ordinary case — but a menu-button click is not the ordinary case: ServerGamePacketListenerImpl.handleContainerButtonClick calls it directly, in the same handler, as soon as AbstractContainerMenu.clickMenuButton accepts. The sword’s slot no longer matches, so ClientboundContainerSetSlotPacket goes out, and only the patch crosses: ItemStack.OPTIONAL_STREAM_CODEC writes the count, Item.STREAM_CODEC (a registry id) and the patch. The client answers later clicks with a HashedStack of CRC32C checksums rather than stacks (codecs, NBT and JSON for the hashing, containers and menus for the click protocol).

The client rebuilds against its own prototype. The decoder constructs ItemStack from holder, count and patch, and that constructor is PatchedDataComponentMap.fromPatch against the client’s own bound prototype — the map RegistryDataCollector bound at the end of configuration. That is the reason components must be bound on the client before the play phase: an unbound prototype would throw in a packet decoder on a Netty thread. ClientPacketListener.handleContainerSetSlot then hands the stack to AbstractContainerMenu.setItem, and the tooltip’s purple line is ItemEnchantments.addToTooltip reading the same component.

Components on things that are not items

Block entities, both directions. Placing runs BlockEntity.applyComponentsFromItemStack, which hands subclasses a recording DataComponentGetter: whatever BlockEntity.applyImplicitComponents reads is forgotten from the patch (DataComponentPatch.forget) and only the leftovers persist as opaque BlockEntity.components. Two types are pre-seeded into that forget set regardless of whether anything reads them — DataComponents.BLOCK_ENTITY_DATA and DataComponents.BLOCK_STATE — and only the added half of the resulting patch is kept, so removals are discarded. Breaking or picking runs the reverse, BlockEntity.collectComponents over BlockEntity.collectImplicitComponents, with BlockEntity.removeComponentsFromTag de-duplicating what was promoted; BlockItem.setBlockEntityData is the write path for the opaque blob.

Entities, read-only. Entity implements DataComponentGetter with no patch of its own: Entity.get answers DataComponents.CUSTOM_NAME and DataComponents.CUSTOM_DATA by hand, lets subclass overrides (Sheep, Wolf, Villager …) answer the variant-shaped types, and otherwise falls through to its EntityType holder’s bound prototype — the same map every registry element gets. Entity.applyComponentsFromItemStack is the write path, and it is not only spawn eggs: any item-to-entity spawn, an arrow picking up its stack, a lingering potion’s cloud and BlockItem all take it.

Where to look

DataComponentType · DataComponents · DataComponentMap · PatchedDataComponentMap · DataComponentPatch · DataComponentInitializers · Holder.Reference · DataComponentLookup · Item.Properties · ItemStack · ItemInstance · ItemStackTemplate · EnchantmentMenu · AbstractContainerMenu · BlockEntity · Entity (the getter half)


Rules: names, never code · how the system works, not how the code reads · newest version only · every backticked name passes tools/verify_names.py.