Closed most remaining UNCONFIRMED items + caught corrections: 1.7 data[]=proto5, 1.8 multi-block nibble order (z/x), stance y+1.62, 1.15 wolf idx18=begging, 1.18 below-zero sections NOT stripped (claim was wrong), CONTAINER_SLOT_STATE 1.20.3, + pinned first-appeared versions (report details/server links=1.21, fluidCount=26.1, block-state id14=1.19.4, itemCount VarInt=1.20.5). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
17 KiB
Entity Metadata Wire Format
Scope: Java Edition entity metadata (a.k.a. "entity data") as carried by
Set Entity Metadataand (historically) inside spawn packets.
Primary sources:
/tmp/mcproto-refs/minecraft-data/data/pc/*/protocol.json—entityMetadata,entityMetadataEntry,entityMetadataItemtype defs/tmp/mcproto-refs/ViaVersion/api/src/main/java/com/viaversion/viaversion/api/type/types/entitydata/OldEntityDataType.java— legacy frame reader/tmp/mcproto-refs/ViaVersion/api/src/main/java/com/viaversion/viaversion/api/type/types/entitydata/ModernEntityDataType.java— modern frame reader/tmp/mcproto-refs/ViaVersion/api/src/main/java/com/viaversion/viaversion/api/minecraft/entitydata/types/EntityDataTypes*.java— per-version type registries- Cross-links: ../versions/1.9.md · ../versions/1.14.md · ../01-data-types.md (VarInt / NBT / Slot)
1. Overview
Entity metadata is an indexed key/value list sent by the server to describe the current state of an entity: health flags, custom names, riding status, arm poses, and hundreds of entity-class-specific attributes. It travels in two contexts:
Set Entity Metadata(Play CB) — differential update for an already-spawned entity. Only the changed indices are sent.- Spawn packets (historical, ≤ 1.8) — the full metadata list was embedded in
Spawn MobandSpawn Object.
The format has two incompatible eras:
| Era | Versions | Index encoding | Type encoding | Terminator |
|---|---|---|---|---|
| Legacy | ≤ 1.8 (protocol ≤ 47) | low 5 bits of a single byte | high 3 bits of the same byte | 0x7F (127) |
| Modern | ≥ 1.9 (protocol ≥ 107) | separate unsigned byte | separate VarInt | 0xFF (255) |
2. Modern Format (1.9+)
Source: ModernEntityDataType.java:34–38; minecraft-data data/pc/1.9/protocol.json (and all later versions).
2.1 Frame structure
EntityMetadata = (Entry)* Terminator
Entry = {
Index : u8 -- unsigned byte; 0x00–0xFE are valid indices
Type : VarInt -- type ID from the per-version registry (see §3)
Value : <depends on Type>
}
Terminator = 0xFF -- single byte; signals end of list
Read loop (pseudocode matching ModernEntityDataType.java):
loop:
index = readUnsignedByte()
if index == 0xFF: break
typeId = readVarInt()
type = registry.byId(typeId)
value = type.read(buffer)
entries.add(Entry(index, type, value))
Key points:
- Index and Type are always present together; neither is omitted.
- The list is unordered within the packet (indices need not be ascending).
- An empty metadata list is a legal single byte:
FF. - The
TypeVarInt is always small (≤ 2 bytes on the wire) but must be read as a full VarInt.
Note on minecraft-data 1.9–1.12.2:
minecraft-datarecords thetypefield asi8(signed byte) in those versions rather thanvarint. This is an artifact of minecraft-data's protocol description — since values 0–12 all fit in one byte, both readings are wire-compatible. ViaVersion'sModernEntityDataTypereads it as VarInt from 1.9 onward, and the Minecraft wiki specifies VarInt. Thevarintencoding is authoritative;i8in minecraft-data is a pragmatic approximation. The minecraft-data source confirms the change tovarintin its 1.13 JSON:/tmp/mcproto-refs/minecraft-data/data/pc/1.13/protocol.json.
3. Type Registry — 1.21.1
Source: EntityDataTypes1_21.java (lines 33–63) + minecraft-data data/pc/1.21.1/protocol.json entityMetadataEntry type mapper.
| ID | Name | Wire encoding |
|---|---|---|
| 0 | Byte | i8 — signed byte |
| 1 | VarInt | VarInt |
| 2 | VarLong | VarLong (added 1.19.3; see §5) |
| 3 | Float | f32 — big-endian IEEE 754 single |
| 4 | String | VarInt length + UTF-8 bytes |
| 5 | Chat (TextComponent) | Anonymous NBT tag (1.20.3+); string (JSON text) in 1.9–1.20.2 |
| 6 | Optional Chat | bool present-flag + Chat (if present) |
| 7 | Slot | Item stack — see ../01-data-types.md §Slot; encoding changed at 1.9, 1.13, 1.13.2, 1.20.2, 1.20.5 |
| 8 | Boolean | bool — single byte 0x00/0x01 |
| 9 | Rotations | f32 pitch + f32 yaw + f32 roll (3 × 4 bytes) |
| 10 | Position | 64-bit packed Position — [x:26][z:26][y:12] since 1.14; [x:26][y:12][z:26] in 1.9–1.13 |
| 11 | Optional Position | bool present-flag + Position (if present) |
| 12 | Direction | VarInt enum: 0=Down 1=Up 2=North 3=South 4=West 5=East |
| 13 | Optional UUID | bool present-flag + 128-bit UUID big-endian (if present) |
| 14 | Block State | VarInt flat block-state ID (0 = air) |
| 15 | Optional Block State | VarInt: 0 = absent; non-zero = block-state ID |
| 16 | NBT | Anonymous compound NBT (no leading name) — see ../01-data-types.md §NBT |
| 17 | Particle | Typed Particle compound (added 1.13) |
| 18 | Particles | VarInt count + Particle[] (added 1.20.5) |
| 19 | Villager Data | VarInt villagerType + VarInt villagerProfession + VarInt level (added 1.14) |
| 20 | Optional VarInt | VarInt: 0 = absent; value = stored_value + 1 |
| 21 | Pose | VarInt enum — see §4.3 (added 1.14) |
| 22 | Cat Variant | VarInt registry ID (added 1.19) |
| 23 | Wolf Variant | registryEntryHolder: VarInt registry ID or inline WolfVariant struct (added 1.21.2; was plain VarInt in 1.21) |
| 24 | Frog Variant | VarInt registry ID (added 1.19) |
| 25 | Optional Global Position | bool present-flag + dimension String + Position (if present) |
| 26 | Painting Variant | registryEntryHolder: VarInt registry ID or inline PaintingVariant struct (added 1.21; struct encoding differs 1.21 vs 1.21.2+) |
| 27 | Sniffer State | VarInt enum (added 1.20) |
| 28 | Armadillo State | VarInt enum (added 1.20.5) |
| 29 | Vector3 | f32 x + f32 y + f32 z (added 1.20) |
| 30 | Quaternion | f32 x + f32 y + f32 z + f32 w (added 1.20) |
Total: 31 type IDs (0–30) in 1.21.x.
Source: EntityDataTypes1_21.java constructor argument super(31).
3.1 registryEntryHolder encoding (types 23, 26)
Used since 1.21 for Wolf Variant and Painting Variant.
RegistryEntryHolder = {
hasInlineData : bool
if hasInlineData:
inlineValue : <variant-specific struct>
else:
registryId : VarInt -- numeric ID within the server's registry
}
WolfVariant inline struct (1.21): wildTexture String + tameTexture String + angryTexture String + biome IDSet.
PaintingVariant inline struct (1.21): i32 width + i32 height + String assetId + Optional Anonymous NBT title + Optional Anonymous NBT author.
Source: EntityMetadataWolfVariant / EntityMetadataPaintingVariant in minecraft-data data/pc/1.21.1/protocol.json.
4. Type Registry History (selected versions)
The per-version type classes in ViaVersion are authoritative. Below are the salient snapshots.
4.1 1.9–1.12.2 (EntityDataTypes1_9 / EntityDataTypes1_12)
Source: EntityDataTypes1_9.java (ordinal = ID), EntityDataTypes1_12.java.
| ID | 1.9 | 1.12 (added) |
|---|---|---|
| 0 | Byte | same |
| 1 | VarInt | same |
| 2 | Float | same |
| 3 | String | same |
| 4 | Component (JSON text) | same |
| 5 | Slot (ITEM1_8) |
same |
| 6 | Boolean | same |
| 7 | Rotations | same |
| 8 | Position (BLOCK_POSITION1_8) |
same |
| 9 | Optional Position | same |
| 10 | Direction | same |
| 11 | Optional UUID | same |
| 12 | Optional Block State | same |
| 13 | — | NBT (NAMED_COMPOUND_TAG) |
Note: no VarLong, no Particle, no Villager Data, no Pose in this era.
4.2 1.13–1.13.2 (EntityDataTypes1_13 / EntityDataTypes1_13_2)
Source: EntityDataTypes1_13.java, EntityDataTypes1_13_2.java.
Key changes:
- Type field becomes VarInt (was
i8-compatible byte in 1.9–1.12.2; explicitlyvarintin minecraft-data from 1.13). - ID 5 becomes
Optional Component(was absent; Component shifts from id 4 to id 4, Optional Component added at 5; ids 5–13 shift up by 1). - Particle added at ID 15.
- Slot encoding updated to
ITEM1_13/ITEM1_13_2.
Full 1.13.2 registry (16 entries, IDs 0–15):
Byte · VarInt · Float · String · Component · OptComponent · Slot · Boolean · Rotations · Position · OptPosition · Direction · OptUUID · OptBlockState · NBT · Particle
4.3 1.14 (EntityDataTypes1_14)
Source: EntityDataTypes1_14.java.
Added IDs (shifts all subsequent IDs by +1 relative to 1.12, due to Optional Component insertion in 1.13 and now Villager Data + Opt VarInt + Pose):
| New ID | Type | Notes |
|---|---|---|
| 16 | Villager Data | VarInt×3: villagerType, villagerProfession, level |
| 17 | Optional VarInt | 0 = absent; otherwise stored_value + 1 |
| 18 | Pose | VarInt enum |
Pose values (1.14): 0=Standing 1=FallFlying 2=Sleeping 3=Swimming 4=SpinAttack 5=Sneaking (1.14 original list)
Position encoding also changes: BLOCK_POSITION1_14 ([x:26][z:26][y:12] bit layout) replaces BLOCK_POSITION1_8. See ../versions/1.14.md §Position.
4.4 1.19 (EntityDataTypes1_19)
Source: EntityDataTypes1_19.java.
Added IDs 19–22:
- 19: Cat Variant (VarInt)
- 20: Frog Variant (VarInt)
- 21: Optional Global Position (
bool+ dimension String + Position) - 22: Painting Variant (VarInt)
4.5 1.19.3 (EntityDataTypes1_19_3)
Source: EntityDataTypes1_19_3.java; confirmed by minecraft-data data/pc/1.19.3/protocol.json.
VarLong inserted at ID 2. All existing IDs from 2 onward shift up by 1 (Float moves to 3, String to 4, etc.). This was the largest single-version numbering shift since 1.13.
Registry after insertion (24 entries):
Byte(0) · VarInt(1) · VarLong(2) · Float(3) · String(4) · Component(5) · OptComponent(6) · Slot(7) · Boolean(8) · Rotations(9) · Position(10) · OptPosition(11) · Direction(12) · OptUUID(13) · OptBlockState(14) · NBT(15) · Particle(16) · VillagerData(17) · OptVarInt(18) · Pose(19) · CatVariant(20) · FrogVariant(21) · OptGlobalPos(22) · PaintingVariant(23)
4.6 1.19.4 (EntityDataTypes1_19_4)
Source: EntityDataTypes1_19_4.java.
Added:
- ID 14: Block State (non-optional, explicit; confirmed new in 1.19.4). ViaVersion
EntityDataTypes1_9throughEntityDataTypes1_19_3have onlyoptionalBlockStateType(never a separateblockStateType);EntityDataTypes1_19_4.java:45is the first to addblockStateType = add(14, Types.VAR_INT)alongsideoptionalBlockStateType = add(15, Types.VAR_INT). Source: grep acrossEntityDataTypes1_9.java,EntityDataTypes1_14.java,EntityDataTypes1_19.java,EntityDataTypes1_19_3.java,EntityDataTypes1_19_4.java. - ID 25: Sniffer State (VarInt enum, new in 1.20 — but the type ID slot is first defined in ViaVersion's 1.19.4 class)
- ID 26: Vector3F (
f32×3) - ID 27: Quaternion (
f32×4)
4.7 1.20.5 (EntityDataTypes1_20_5) and 1.21 (EntityDataTypes1_21)
Source: EntityDataTypes1_20_5.java, EntityDataTypes1_21.java.
Changes vs 1.19.4:
- Particles (plural) added at ID 18. Particle (singular) stays at 17. All subsequent IDs shift +1.
- Chat/Component now uses anonymous NBT tag (
TRUSTED_TAG) instead of JSON string. Source:Types.TRUSTED_TAG/Types.TRUSTED_OPTIONAL_TAGin both files. - Wolf Variant (ID 23): 1.21 uses
WolfVariant.TYPE(inline struct holder); 1.20.5 used plain VarInt. - Armadillo State (ID 28) added.
- Wolf Variant, Painting Variant use
registryEntryHoldershape from 1.21.2 onward.
Full 1.21.x registry: see §3 above (31 entries, IDs 0–30).
5. Evolution Timeline
| Version | Change |
|---|---|
| ≤ 1.8 | Legacy single-byte packed format (§6); terminator 0x7F |
| 1.9 | Modern format: separate u8 index + VarInt type + 0xFF terminator; 13 type IDs |
| 1.9 | Dual-hand: HAND_ACTIVE (Byte, index 5 on LivingEntity) added; off-hand Slot carried in Slot type; old Block/Int types removed |
| 1.12 | NBT type added (ID 13); total 14 type IDs |
| 1.13 | Type field officially becomes VarInt (was byte-range compatible); Optional Component added (ID 5); Particle added (ID 15); Slot encoding updated; 16 type IDs |
| 1.14 | Villager Data, Optional VarInt, Pose added (IDs 16–18); Position bit-layout changed ([x:26][z:26][y:12]) |
| 1.19 | Cat Variant, Frog Variant, Optional Global Position, Painting Variant added (IDs 19–22); 23 type IDs |
| 1.19.3 | VarLong inserted at ID 2 — all subsequent IDs +1; 24 type IDs |
| 1.19.4 | Block State (explicit non-optional) ID 14 split from Optional Block State; Sniffer State, Vector3, Quaternion added; 28 type IDs |
| 1.20.5 | Particles (plural) added at ID 18 (+1 shift); Chat type switches from JSON string to anonymous NBT; Armadillo State added; Wolf Variant expanded; 31 type IDs |
| 1.21 | Wolf Variant becomes registryEntryHolder; Painting Variant becomes registryEntryHolder |
| 1.21.2 | Painting Variant inline struct encoding updated |
6. Legacy Format (≤ 1.8)
Source: OldEntityDataType.java:36–50; minecraft-data data/pc/1.8/protocol.json entityMetadata.
6.1 Frame structure
EntityMetadata = (Entry)* Terminator
Entry = {
header : u8 -- bits [7:5] = type ID (3 bits); bits [4:0] = index (5 bits)
Value : <depends on type ID>
}
Terminator = 0x7F -- single byte (127); index field would be 31, type 3 = Float, but 0x7F is the sentinel
The header byte encoding:
header = (typeId << 5) | (index & 0x1F)
This limits index to 0–31 (5 bits) and type to 0–7 (3 bits).
6.2 1.8 type registry (8 entries)
Source: EntityDataTypes1_8.java (ordinal = ID); minecraft-data data/pc/1.8/protocol.json entityMetadataItem.
| ID | Name | Wire encoding |
|---|---|---|
| 0 | Byte | i8 |
| 1 | Short | i16 big-endian |
| 2 | Int | i32 big-endian |
| 3 | Float | f32 IEEE 754 |
| 4 | String | VarInt length + UTF-8 |
| 5 | Slot | ITEM1_8 — item id (i16) + count (i8) + damage (i16) + NBT |
| 6 | Block Position | i32 x + i32 y + i32 z (three separate ints, NOT the packed Position) |
| 7 | Rotations | f32 pitch + f32 yaw + f32 roll |
Key differences from 1.9+:
- No Boolean type (boolean flags used Byte 0/1).
- No Optional types (UUID, Position, Block State).
- Short (i16) and Int (i32) exist; both removed in 1.9 (replaced by VarInt).
- Block Position is three plain
i32fields, not a packed 64-bit value. - No Component / NBT / Direction / Particle.
7. Index Assignments Are Per-Entity-Class and Per-Version
The type registry (§3) defines only the wire encoding per type ID. Which type ID and which index value mean what for a given entity class is entirely version-specific and entity-class-specific.
Examples (from EntityDataIndex1_9.java):
| Entity class | Index (1.8) | Index (1.9) | Type (1.8) | Type (1.9) | Meaning |
|---|---|---|---|---|---|
| Entity | 0 | 0 | Byte | Byte | Status flags (on fire, crouching, …) |
| Entity | 1 | 1 | Short | VarInt | Air supply |
| Entity | 2 | 2 | String | String | Custom name |
| LivingEntity | 6 | 6 | Float | Float | Health |
| LivingEntity | 7 | 7 | Int | VarInt | Potion effect colour |
| Player | 10 | 12 | Byte | Byte | Skin flags |
| LivingEntity | — | 5 | — | Byte | Hand state (new in 1.9, dual-hand) |
Per-version index tables live in the ViaVersion EntityDataIndex files under
/tmp/mcproto-refs/ViaVersion/common/src/main/java/com/viaversion/viaversion/protocols/v1_8to1_9/data/EntityDataIndex1_9.java
and in EntityTypes* classes for later versions.
For 1.21.1 entity data index assignments, refer to ../versions/1.21.md or the Minecraft wiki Entity format page which tabulates per-class indices.
8. Carrying Packet
In 1.21.1 (Play state, client-bound):
Set Entity Metadata (0x52) = {
entityId : VarInt
metadata : EntityMetadata -- zero or more entries + 0xFF
}
Source: minecraft-data data/pc/1.21.1/protocol.json play.toClient.types.packet_entity_metadata.
Historically (≤ 1.8) the full metadata list was also embedded in Spawn Mob (CB 0x0F) and Spawn Object (CB 0x0E).
9. Open Items
- Block State (ID 14, non-optional) first appeared in 1.19.4: confirmed — ViaVersion's 1.14, 1.19, and 1.19.3 classes have only
optionalBlockStateType; 1.19.4 adds bothblockStateType(14) andoptionalBlockStateType(15). See §4.6. optional_global_poswire encoding: confirmed —GlobalBlockPositionType.javareadsString dimension(the dimension ResourceLocation, e.g.minecraft:overworld) +BLOCK_POSITION1_14(packed i64 position). Theminecraft-data['option', 'string']representation is a schema approximation; the actual wire format isbool+ (if present) String + packed Position. Source:ViaVersion/.../api/type/types/math/GlobalBlockPositionType.java:41-43.- Pose enum values beyond the 1.14 original (e.g.
Croaking,UsingTongue,Roaring,Sniffing,Emerging,Diggingadded for 1.17–1.20 mobs) — not enumerated here; the authoritative list is atminecraft.wiki/w/Entity_format#Pose.