<!-- https://minecraftdocs.dev/systems/foundations/identifiers-and-registries — this page as markdown. The book's index: https://minecraftdocs.dev/llms.txt; the whole book in one file: https://minecraftdocs.dev/llms-full.txt -->

# Identifiers and registries

> Verified against **Minecraft 26.3** · Part II · A player types `/give @s minecraft:diamond_sword`, and the sword reaches their inventory as the number of the line it was registered on.

A player types `/give @s minecraft:diamond_sword`. The string on the right
is parsed into an `Identifier`, the identifier is paired with
`Registries.ITEM` to make a `ResourceKey`, and the key is looked up in a
table the server froze at startup, before any world existed —
`BuiltInRegistries.ITEM`. The stack that then lands in the player's
inventory does not carry the name. `Item.STREAM_CODEC` is
`ByteBufCodecs.holderRegistry` over `Registries.ITEM`, and it writes an
integer. That integer is a line number. `MappedRegistry.byId` is an
insertion-ordered list, `MappedRegistry.register` appends to it, and an
entry's numeric id is its position — so the wire id of a diamond sword is
where its line falls in `Items`, reordering two lines in `Blocks` changes a
block's wire id, and a resource pack cannot. A dynamic registry, a biome
from a data pack, gets its numbers the other way: its elements are decoded
in parallel but registered in sorted order of their ids, so the server's
numbering does not depend on which file finished first. The client does not
re-derive those numbers at all — it registers what the server sent, in the
order the packet lists them.

## The cast

| class | what it decides | thread |
|---|---|---|
| `Identifier` | the name — namespace and path — and which strings are legal ones | any |
| `ResourceKey` | the name paired with the registry it belongs to, interned so equal keys are one object | any |
| `Registry` · `MappedRegistry` | the table: key to object to integer, the frozen flag, the two tag tables | elements written on the launching thread or a load task's worker, tags by whichever thread applies them; read from anywhere |
| `Holder` | a reference to an entry that may be handed out before the entry exists (`Holder.Reference`), or an inline value that belongs to no registry (`Holder.Direct`) | any |
| `HolderLookup.Provider` · `RegistryAccess` | the read-only view a codec resolves against: all the registries it may name | any |
| `BuiltInRegistries` | the static registries — created empty at class init, filled and frozen by `Bootstrap.bootStrap` | the launching thread, before any server or client object exists |
| `RegistryDataLoader` | the dynamic registries — one load task per registry, JSON from the packs on the server; on the client NBT from the wire, or its own jar's JSON for a pack both sides know | `Util.backgroundExecutor` |
| `LayeredRegistryAccess` | which layer may see which: four `RegistryLayer`s on the server, two `ClientRegistryLayer`s on the client | the server thread owns `MinecraftServer.registries`; the Render thread owns the client's |

All of this is server *and* client: `MappedRegistry`, `BuiltInRegistries`
and `RegistryDataLoader` ship in the dedicated server jar. Client-only, of
the classes this page names, are `ClientRegistryLayer`,
`RegistryDataCollector`, `KnownPacksManager` and the client's
`CommonListenerCookie` — and, less surprisingly,
`ClientPacketListener`, `ClientConfigurationPacketListenerImpl` and
`IntegratedServer`.

## The name

`Identifier`, in `net/minecraft/resources`, is a namespace and a path, and
it is the class a 1.21-era reader knows as *ResourceLocation*. It is not a
record: a final class with a private constructor whose validation is an
assertion on the trusted path and a real check on the parsing paths
(`Identifier.isValidNamespace`, `Identifier.isValidPath`). The two
character sets differ, and the difference bites: a **path** may contain
`/`, a **namespace** may not, and `..` is rejected outright as a namespace.
`Identifier.DEFAULT_NAMESPACE` is *minecraft*, and
`Identifier.withDefaultNamespace` supplies it — as does parsing a string
with no separator at all, or one that starts with the separator.
`Identifier.parse` throws `IdentifierException` (which lives in
`net/minecraft`, not beside `Identifier`) where `Identifier.tryParse`
returns null. `Identifier.read` exists twice, once returning a `DataResult`
for `Identifier.CODEC` and once over a Brigadier `StringReader`, with
`Identifier.readNonEmpty` beside it, and `Identifier.resolveAgainst`
carries a path-traversal guard. The one that surprises people:
**`Identifier.compareTo` orders by path first and namespace second**, so a
sorted list of ids is not grouped by mod.

A `ResourceKey` is an `Identifier` paired with the `Identifier` of the
registry it belongs to, and keys are **interned** through a weak map keyed
by `ResourceKey.InternKey`, so two keys for the same registry and id are
literally the same object. `Registries` holds the 156 `ResourceKey`s *of
registries* (`Registries.ITEM`, `Registries.BIOME` …) — 155 distinct
objects, because two of the keys are one: `Registries.DIMENSION` and
`Registries.LEVEL_STEM` are both built from the string *dimension*, so
interning hands back one object under two names and two (unchecked) element
types — `Registries.LEVEL_STEM` is the data-pack registry the
`RegistryLayer.DIMENSIONS` layer loads, `Registries.DIMENSION` keys the
`ServerLevel`s, and the conversion helpers between them are identity
functions at run time. Five more registry keys are declared by the class
that owns them rather than here, which is why the catalogue's total is 161
and this one is 156 ([registries](../../reference/registries)). Interning
is what makes identity work: `ResourceKey` declares no equality of its own, so
`MappedRegistry.byKey`, `MappedRegistry.registrationInfos`,
`Holder.Reference.is` for a `ResourceKey` and every `==` against a key like
`Level.OVERWORLD` rely on equal keys being one object. `BlockIds`, `ItemIds`
and `BlockItemIds` (`net/minecraft/references`) hold the per-element keys the
block and item initialisers use.

## The table

`Registry` is the read interface — `Registry.getValue`, `Registry.getKey`,
`Registry.getId`, `Registry.getTags`, and the codecs `Registry.byNameCodec`
and `Registry.holderByNameCodec` — and it extends `IdMap`, so every registry
is also an int-to-object table. `WritableRegistry` adds
`WritableRegistry.register` and `WritableRegistry.bindTags`;
`DefaultedRegistry` answers a default entry — *air* for items and blocks —
instead of null. `MappedRegistry` is the one real implementation, with
`DefaultedMappedRegistry` its only subclass. It holds the same entries in
five indexes at once, and every lookup direction is one of them:
`MappedRegistry.byKey` and `MappedRegistry.byLocation` from a name, the
insertion-ordered `MappedRegistry.byId` from a number, and, from the object
itself, the two identity maps `MappedRegistry.byValue` — what answers
`Registry.getKey` — and `MappedRegistry.toId`, which returns −1 for anything
it has never seen, an equal-but-distinct object included. It carries the
`MappedRegistry.frozen` flag that `MappedRegistry.validateWrite` checks on
every ordinary write.

A `Holder` is the seam between a registry and the code that names its
entries. It is a sealed interface with two kinds (`Holder.Kind`).
`Holder.Reference` is an entry *in* a registry — and is itself
`non-sealed`: it knows its `HolderOwner`, its key, its tags and its
components, and any of those may be unbound until the registry binds them
(`Holder.Reference.bindValue`, `Holder.Reference.bindTags`,
`Holder.Reference.bindComponents`). A reference is a promise:
`Holder.Reference.value` throws until `Holder.Reference.bindValue` has run,
codecs hand these out freely during a load, and the freeze is what makes
every promise kept — or fails loudly. `Holder.Direct` is a record wrapping
an inline value and a `DataComponentMap` that belongs to no registry: it has
no key, is in no tag, and serialises inline. A `HolderSet` is a set of
holders — `HolderSet.Named` is a tag, `HolderSet.Direct` a literal list.

What a codec sees is a read-only view. `HolderGetter`, `HolderLookup` and
`HolderOwner` are those views — and `HolderOwner` exists for exactly one
question, `HolderOwner.canSerialize`: the context asking to serialise a
holder answers whether it is that holder's owner, which is why a holder from
one world refuses to be written by another; `HolderLookup.Provider` is "all the
registries I may resolve against" and `HolderLookup.RegistryLookup` is one
of them. `RegistryAccess` is a `HolderLookup.Provider` over a set of
registries, and `RegistryAccess.Frozen` is a bare marker for the finished
kind. Every static initialiser writes its elements through `Registry.register`
or `Registry.registerForHolder` (the registries themselves go into the root
through `WritableRegistry.register`); every
codec that names a *dynamic* entry — `RegistryFileCodec`,
`RegistryFixedCodec`, `RegistryCodecs.holderSet`, `HolderSetCodec` —
resolves through a `RegistryOps` ([codecs, NBT and JSON](codecs-nbt-json#where-the-registry-context-comes-from));
and at runtime `MinecraftServer.registryAccess` and
`ClientPacketListener.registryAccess` are where anything that must resolve a
key goes, except the server's reloadable registries, which
`MinecraftServer.reloadableRegistries` holds.

## Before the game exists

```mermaid
sequenceDiagram
    participant Boot as Bootstrap
    participant BIR as BuiltInRegistries
    participant Items as Items
    participant Item as Item
    participant DMR as Defaulted<br/>MappedRegistry

    Note over Boot,DMR: the launching thread, before any server or client object exists
    Boot->>BIR: class init: one empty registry per key, 95 of the 156, each with a loader
    Note over Boot,Items: the dispenser and cauldron bootstraps run first, so Items is already initialised
    Boot->>Items: class init: registerItem per item, Item.Properties.setId stores the key
    Items->>Item: new Item(properties), which knows its own key
    Item->>DMR: createIntrusiveHolder: a Holder.Reference with a value, no key
    Items->>DMR: register(key, item, BUILT_IN): bindKey, numeric id = byId.size()
    Boot->>BIR: bootStrap: createContents runs every loader, then freeze, then validate
    BIR->>BIR: freeze: the root, then each registry — tags bound empty, then MappedRegistry.freeze
    DMR->>DMR: bindValue on every holder, refuse an unbound one, build componentLookup
    Note over Boot,DMR: components and tags are bound together later, when a world loads
```

*The bootstrap ladder, on one thread, before either program exists. The
numeric id an item gets is the size of the registry when it registers — the
line number of its registration — which is why the wire ids are stable only
because the registration order is.*


Both `Main` classes call `Bootstrap.bootStrap` early, after argument parsing
and crash-report preload ([anatomy](../anatomy/anatomy#from-main-to-a-world)
has the order), on the launching thread before any server or client object
exists, and `Bootstrap.isBootstrapped` is set before any registry is touched. `Bootstrap.bootStrap` calls
`BuiltInRegistries.bootStrap`, and after it returns every built-in registry
is frozen and any `WritableRegistry.register` throws.

**Registries exist before their contents.** `BuiltInRegistries` class init
creates every registry empty and records the loader that fills it in
`BuiltInRegistries.LOADERS`, an insertion-ordered map. `Bootstrap.bootStrap`
then runs `BuiltInRegistries.createContents` — the loaders in that order.
By then `Items`, `Blocks` and `EntityTypes` are already initialised:
`Bootstrap.bootStrap` reaches `FireBlock.bootStrap`,
`EntityTypes.PLAYER`, the dispense behaviours' bootstrap and
`CauldronInteractions.bootStrap` before it calls
`BuiltInRegistries.bootStrap`, and those touch `Blocks`, `EntityTypes` and
`Items` in that order — so
`BuiltInRegistries.createContents` finds the loaders' values already there rather than
triggering them.
`Bootstrap.checkBootstrapCalled` is the guard that makes "touched `Blocks`
from a static initialiser" a crash rather than a silent empty registry; it
works because the bootstrap flag is set *before* the registries are touched,
not after.

**The key travels in the properties.** `Items.registerItem` takes a
`ResourceKey` from `ItemIds` or `BlockItemIds` and calls
`Item.Properties.setId` before constructing, so an `Item` knows its own key at
construction time. Blocks do the same with `BlockIds` and `BlockItemIds` and
`BlockBehaviour.Properties.setId`.

**Five registries hand the object its own holder.** `BuiltInRegistries.BLOCK`,
`BuiltInRegistries.ITEM`, `BuiltInRegistries.FLUID`,
`BuiltInRegistries.ENTITY_TYPE` and `BuiltInRegistries.BLOCK_ENTITY_TYPE`
are created with intrusive holders: the constructor asks the registry for a
`Holder.Reference` (`MappedRegistry.createIntrusiveHolder`) that wraps the
object before it has a key, and stores it in `Item.builtInRegistryHolder`.
Registration then binds the key to *that* holder rather than creating a
new one, so `Item.builtInRegistryHolder` and the registry's own holder are
the same object, and a tag check on a block or item is a set lookup on the
holder's own bound tag set with no registry hop
([tags](tags#the-check-is-a-field-read)). `Holder.Reference.createIntrusive` is marked
deprecated — the mechanism is load-bearing but not encouraged.

**The numeric id is the line number.** `MappedRegistry.register` appends
to `MappedRegistry.byId`, and every static registration carries
`RegistrationInfo.BUILT_IN` — a `Lifecycle` and the `KnownPack` an entry
came from, which for a static entry is nothing. The wire id of an item is
the position of its line in `Items`, and `Item.STREAM_CODEC` encodes that
integer. A dynamic registry numbers the other way, and the difference is
worth holding: `ResourceManagerRegistryLoadTask` decodes its files in
parallel and then registers them in sorted order of their ids, so the
server's numbering is the same whichever file finished first. The client
still never derives a number — it registers what the packet lists, in the
order the packet lists it — but because the server sorted, that order is
reproducible rather than accidental.

**Freeze is a proof**, stated in full below. `BuiltInRegistries.freeze`
freezes the root registry first, then every registry it holds, and
`BuiltInRegistries.validate` closes the bootstrap: an empty registry is
logged, a `DefaultedRegistry` whose default key is missing throws.

## When a world opens

The server keeps its registries as a `LayeredRegistryAccess` in
`MinecraftServer.registries`, one layer per `RegistryLayer` —
`RegistryLayer.STATIC`, `RegistryLayer.WORLD`,
`RegistryLayer.DIMENSIONS`, `RegistryLayer.RELOADABLE`, in that order —
with `MinecraftServer.registryAccess` the flattened view.
`LayeredRegistryAccess.getAccessForLoading` is everything *before* a layer,
which is what that layer's JSON may reference;
`LayeredRegistryAccess.compositeAccess` is everything. The client mirrors
this with two layers, `ClientRegistryLayer.STATIC` and
`ClientRegistryLayer.REMOTE`. On both sides the STATIC layer is
`RegistryAccess.fromRegistryOfRegistries` over `BuiltInRegistries.REGISTRY`
— a live view of the frozen root registry, not a copy of it.

```mermaid
flowchart TD
    WL["WorldLoader.load, on Util.backgroundExecutor"]:::worker
    RDL["RegistryDataLoader.load for the world<br/>registries, then the dimensions"]:::worker
    T1["one RegistryLoadTask: feature"]:::worker
    T2["one RegistryLoadTask: placed feature"]:::worker
    T3["… one per RegistryDataLoader.RegistryData, 51 then 1"]:::worker
    F["RegistryLoadTask.freezeRegistry binds every promise,<br/>then the RegistryValidator"]:::worker
    LRA["LayeredRegistryAccess.replaceFrom:<br/>world and dimensions in one call"]:::worker
    WL --> RDL
    RDL --> T1
    RDL --> T2
    RDL --> T3
    T1 -. "an unbound Holder.Reference" .-> T2
    T2 -. "an unbound Holder.Reference" .-> T1
    T1 --> F
    T2 --> F
    T3 --> F
    F --> LRA
```

*World load, as a task graph rather than a conversation: one task per
registry, all on the worker pool, each able to ask any other in the same call
for an element it has not registered yet. The dotted edges are the forward
references, the freeze is where they stop being promises, and the last box
runs on the worker pool too, not on the thread that called `WorldLoader.load`.*

Later, a client logs in and reaches the configuration phase, and the server
sends it what it just built.

```mermaid
sequenceDiagram
    box transparent the server
    participant SRT as Synchronize<br/>RegistriesTask
    participant SCPL as ServerConfiguration<br/>PacketListenerImpl
    end
    box transparent the client
    participant CCPL as ClientConfiguration<br/>PacketListenerImpl
    participant RDC as RegistryData<br/>Collector
    end

    SRT->>CCPL: ClientboundSelectKnownPacks: which packs do you have?
    CCPL->>SCPL: ServerboundSelectKnownPacks: accepted all-or-nothing
    SRT->>CCPL: one ClientboundRegistryDataPacket per synced registry
    Note over SRT,CCPL: an entry from a pack the client has carries no data
    SRT->>CCPL: ClientboundUpdateTagsPacket: tags as registry ints
    SCPL->>CCPL: ClientboundFinish<br/>ConfigurationPacket
    CCPL->>RDC: collectGameRegistries: rebuild REMOTE, patch the static tags
    RDC->>CCPL: a RegistryAccess.Frozen, into the play listener's cookie
    Note over CCPL,RDC: with an IntegratedServer, that Frozen is thrown away<br/>for the server's own registries
    CCPL->>SCPL: ServerboundFinish<br/>ConfigurationPacket
```

*The same registries crossing to a client: `SynchronizeRegistriesTask` sends
the three data packets, and the fourth, which lets play begin, comes through
the listener from a later task, `JoinWorldTask`. The client decodes none of it
until that finish packet arrives, and the last note is the singleplayer case,
where the work of the whole exchange is discarded.*


`WorldLoader.load` creates the resource manager on the main thread, runs
`RegistryDataLoader.load` on `Util.backgroundExecutor`, and returns to the main
thread for the final assembly; this is where the `RegistryLayer.WORLD` and
`RegistryLayer.DIMENSIONS` layers are filled, and the
`RegistryLayer.RELOADABLE` layer of the copy
`MinecraftServer.reloadableRegistries` holds. The configuration phase is
the third moment: `SynchronizeRegistriesTask` sends the dynamic registries
on the server thread, and the client rebuilds its
`ClientRegistryLayer.REMOTE` layer in
`RegistryDataCollector.collectGameRegistries` — decoding on the worker
pool, joined on the Render thread — before it will accept play packets.

**Layers load against the layers before them.** `RegistryDataLoader.load`
is given lookups built from `LayeredRegistryAccess.getAccessForLoading` —
built by `TagLoader.buildUpdatedLookups`, so that the static registries'
freshly read tags are visible to the worldgen codecs before they are
applied ([tags](tags#the-four-moments-tags-are-loaded)) — so a biome JSON may reference a placed feature
(same layer) or a sound event (`RegistryLayer.STATIC`) but never a level
stem (`RegistryLayer.DIMENSIONS`, which loads after). The lists
`RegistryDataLoader.WORLD_REGISTRIES`,
`RegistryDataLoader.DIMENSION_REGISTRIES`,
`RegistryDataLoader.RELOADABLE_REGISTRIES` and
`RegistryDataLoader.SYNCHRONIZED_REGISTRIES` say which keys belong to which
step and which subset the client is told about. Both the world and the
dimensions layers are installed in a *single* `LayeredRegistryAccess.replaceFrom`
call, and the dimensions layer that wins is the world data's, not
necessarily the one just decoded — a saved world's dimension set survives.

**Loading is a task graph.** Each registry is one `RegistryLoadTask`
owning a fresh `MappedRegistry` and a lock-guarded
`ConcurrentHolderGetter`. `RegistryDataLoader.createContext` hands every
task's getter to every other, so `Biome.DIRECT_CODEC` decoding on one
worker can ask for a carver that another worker is still
registering — the getter returns an unbound `Holder.Reference`, and the
reference is bound when that registry freezes. Forward references cost
nothing, even in a cycle — a feature can name placed features and a placed
feature its feature — because nothing waits on them.
Fourteen of the fifty-two world and dimension registries also carry a
`RegistryValidator` other than the empty one in their `RegistryDataLoader.RegistryData`, run after
the freeze — thirteen of them entity-variant registries running the same
check, `RegistryValidator.nonEmpty`, and the fourteenth `Registries.TIMELINE`,
whose `Timeline.validateRegistry` is its own.

**Provenance is recorded per entry, and it is coarser than it looks.**
`ResourceManagerRegistryLoadTask` gives each element a `RegistrationInfo`
naming the `KnownPack` it came from and a `Lifecycle`. The rule is
*presence*, not vanilla-ness: an element from **any** pack that reports a
`KnownPack` is stable, and only an element from a pack with no known-pack
info is experimental. `KnownPack.isVanilla` is computed on that path and
then discarded. Anything received over the network is experimental, and
the `RegistryLayer.RELOADABLE` layer is read by the same task under the same
rule; the registry's own lifecycle is the merge of its entries', and that merge is
what the "experimental features" warning on world open reads.

**The client is told what it does not already have.**
`SynchronizeRegistriesTask` first asks the client which `KnownPack`s it has
(`ClientboundSelectKnownPacks`). The request lists only the server's packs
that *declare* one — `PackLocationInfo.knownPackInfo` is an optional, so a
world's own datapack is simply absent from it — and the client matches them
against its bundled vanilla repository through
`KnownPacksManager.trySelectingPacks`. The comparison is **all or nothing**:
the
client's answer must equal the request exactly, or every element of every
synced registry is sent in full. On a match,
`RegistrySynchronization.packRegistries` sends every element's *id* but
leaves the NBT payload empty for entries from those packs, and the client's
`RegistryLoadTask.PendingRegistration.findAndLoadFromResource` re-decodes
the JSON from its own jar. A modified biome from a custom data pack is sent
in full through `Biome.NETWORK_CODEC` — the network codec, which omits the
generation and mob-spawn settings the client never needs.

**The client rebuilds one layer and patches the other.**
`RegistryDataCollector` accumulates the packets, and
`RegistryDataCollector.collectGameRegistries` runs when configuration
finishes — dispatching the load onto a background executor against the
negotiated packs and then blocking on the result, so the decoding leaves the
Render thread, which waits for it, without the phase becoming asynchronous. The `ClientRegistryLayer.REMOTE` layer is rebuilt wholesale and
frozen — but the **static** registries cannot be rebuilt, so their tags are
applied in place, through the mechanism [tags](tags#the-four-moments-tags-are-loaded) owns, and when no
registry data arrived at all the collector takes a tags-only path that
patches and returns the original registries untouched. The result is a
`RegistryAccess.Frozen` in `CommonListenerCookie` that every
`RegistryFriendlyByteBuf` in the play phase decodes against.

**Singleplayer throws most of that away.** When an `IntegratedServer`
exists, `ClientConfigurationPacketListenerImpl.handleConfigurationFinished`
substitutes the server's own registries for the ones the client just built,
and the memory connection suppresses re-applying static tags and components
client-side. Both halves then share the same registry objects — which is
worth remembering whenever a page says "the client's copy".

## The freeze rule, stated

A frozen registry's **contents** never change. Its **tags** and its
elements' **components** do. Everything in this part that looks like an
exception to the first sentence is one of the two things in the second.

`MappedRegistry.freeze` is a proof, not a switch. It binds every holder's
value and throws if any holder is still unbound, if any intrusive holder
was created but never registered, or if any tag declared by a `TagKey` was
never bound. The tag half of that proof works because a registry keeps its
*declared* tags apart from its *bound* ones, which is the split
[tags](tags#a-tag-is-a-key-and-a-file) is built on: the freeze checks the
declared table and installs the bound one. For the static registries the
real tags do not exist until a data pack is read, so `BuiltInRegistries.freeze`
first binds the tags the bootstrap asked for to empty
(`MappedRegistry.bindAllTagsToEmpty`) and the proof passes on empty sets.
The freeze also builds `MappedRegistry.componentLookup`. After it, the
`MappedRegistry.frozen` flag makes `MappedRegistry.validateWrite` throw on
every ordinary write. Two things still change: `MappedRegistry.prepareTagReload`
*requires* the frozen flag, and the component prototypes are rebound beside
the tags ([data components](data-components)).

What changes afterwards changes through two doors. **Tags:** a world load
swaps the tag tables of the static registries, and `/reload` does more
than refill the `RegistryLayer.RELOADABLE` layer — it re-reads and
re-applies tags for **every** registry in the server's composite access.
How a frozen registry's tags are swapped is the pay-off of
[tags](tags#prepared-then-applied), and the mechanics of the reload
itself belong to [the resource system](resource-system#reload-the-same-pipeline-on-the-server). **Components:** components are not part of
the freeze at all. Every registry element's `DataComponentMap` is bound
after it by
`Holder.Reference.bindComponents`, and `/reload` rebinds every one of them;
[data components](data-components#the-prototype-and-why-it-is-built-at-reload) owns how.

## Feature flags: the same registry, narrowed

A frozen registry's contents never change — but what a *lookup* will show you
can be narrower than what the registry holds, and that is most of the
feature-flag mechanism; the rest is a direct `FeatureElement.isEnabled` check
where an element is used. `FeatureFlagSet` is a bitmask: a 64-bit *long* and the
`FeatureFlagUniverse` it belongs to, with `FeatureFlagSet.MAX_CONTAINER_SIZE`
at 64 flags. There is one universe, *main*, and `FeatureFlags` declares four
flags in it — `FeatureFlags.VANILLA` and the three experiments,
`FeatureFlags.TRADE_REBALANCE`, `FeatureFlags.REDSTONE_EXPERIMENTS` and
`FeatureFlags.MINECART_IMPROVEMENTS`. A set that is not a subset of
`FeatureFlags.VANILLA_SET` is what `FeatureFlags.isExperimental` reports, and
what puts the warning on a world.

What carries a flag is a registry element. `FeatureElement` is an interface
whose one abstract method is `FeatureElement.requiredFeatures`, implemented by exactly
seven types — `Item`, `BlockBehaviour`, `EntityType`, `GameRule`, `MenuType`,
`Potion` and `MobEffect` — and `FeatureElement.FILTERED_REGISTRIES` names the
seven registries those live in. `HolderLookup.RegistryLookup.filterFeatures`
is the filter: handed the world's enabled set, it returns the same lookup
untouched for any registry not in that set of seven, and a delegating lookup
that hides the disabled elements for one that is. The registry underneath is
not touched, and neither is its numbering — a disabled item keeps its wire id.

That is why an experiment is enabled per world and not per install. The set
is `WorldDataConfiguration.enabledFeatures`, read from *level.dat*, and the
filtered lookup is what `CommandBuildContext` hands every argument type, so
`/give` cannot name a disabled item and `/setblock` cannot name a disabled
block; `GameRules` builds its map from the filtered `BuiltInRegistries.GAME_RULE`,
so a disabled rule is not merely refused but absent; and `LevelReader.holderLookup`
filters for anything that asks a level for a registry. Where the flags come
*from* is the pack stack — a feature pack is a built-in pack carrying a
`FeatureFlagsMetadataSection`, and turning on an experiment is enabling a
data pack ([the resource system](resource-system#discover-the-repository-and-its-packs)).

## What crosses the wire, and where the files are

Built-in registry *elements* never cross the network — both sides ran the
same static initialisers — but their *tags* do, and dynamic elements do:
`ClientboundSelectKnownPacks` and `ServerboundSelectKnownPacks`, then
`ClientboundRegistryDataPacket` (one per synchronised registry, entries as
`RegistrySynchronization.PackedRegistryEntry`) in the configuration phase,
then `ClientboundUpdateTagsPacket`, which is a **common** packet and arrives
again mid-play after a server `/reload`; and a registry element,
built-in or dynamic, crosses inside other packets as a bare varint id
resolved against the buffer's registry access, except in the few packets
that send its key. Only one variant shifts that
numbering: `ByteBufCodecs.holder` reserves 0 for an inline `Holder.Direct`
and writes every registry id one higher, where `ByteBufCodecs.holderRegistry`
— which `Item.STREAM_CODEC` uses — writes the raw id. On disk, every key in
`RegistryDataLoader.WORLD_REGISTRIES` and
`RegistryDataLoader.DIMENSION_REGISTRIES` reads
`data/<namespace>/<registry path>/*.json` (`Registries.elementsDirPath`)
through `FileToIdConverter.registry` over a `ResourceManager`
([the resource system](resource-system#snapshot-the-manager)), its tags live under
`Registries.tagsDirPath` — there is a third path builder,
`Registries.componentsDirPath`, but it names a *reports* directory the data
generator writes and nothing in the running game reads — and the reloadable set (loot
tables, predicates, item modifiers, recipes, advancements and three more) is
read the same way, through `ReloadableServerRegistries`. Which registry is which kind is
[reference/registries](../../reference/registries).

> **For a 1.21-era reader.** `Identifier` was *ResourceLocation*, and the vanilla
> data you remember being registered in code is now read as JSON like anyone
> else's: `RegistrySetBuilder`, `BootstrapContext` and `VanillaRegistries` are
> the *data generator* that writes those files into the jar, and the running game
> only ever reads them ([what this book skips](../anatomy/what-this-book-skips#the-data-generators-and-why-data-driven-is-both-true-and-misleading)
> has how far that is true). And `Block.BLOCK_STATE_REGISTRY` is not a registry:
> `IdMapper` is the standalone `IdMap` behind block-state ids and similar
> palettes, sharing an interface with `Registry` and nothing else.

## Where to look

`Identifier` · `ResourceKey` · `Registries` · `Registry` · `MappedRegistry.register` ·
`MappedRegistry.freeze` · `DefaultedMappedRegistry` · `Holder` · `HolderSet` ·
`HolderLookup` · `Bootstrap.bootStrap` · `BuiltInRegistries.bootStrap` ·
`RegistryLayer` · `LayeredRegistryAccess` · `WorldLoader.load` ·
`RegistryDataLoader.load` · `RegistryLoadTask` ·
`ResourceManagerRegistryLoadTask` · `RegistryValidator` ·
`RegistrySynchronization.packRegistries` · `SynchronizeRegistriesTask` ·
`RegistryDataCollector.collectGameRegistries` · `NetworkRegistryLoadTask` ·
`ClientConfigurationPacketListenerImpl.handleConfigurationFinished` · `RegistryOps`

---

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