Files
minecraft_protocol/packets/format-entity-metadata.md
T
claude-timemachine 02a249a379 verify pass 2: resolve markers via ViaBackwards/ViaRewind/ViaLegacy
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>
2026-06-19 19:13:19 +02:00

17 KiB
Raw Blame History

Entity Metadata Wire Format

Scope: Java Edition entity metadata (a.k.a. "entity data") as carried by Set Entity Metadata and (historically) inside spawn packets.
Primary sources:

  • /tmp/mcproto-refs/minecraft-data/data/pc/*/protocol.jsonentityMetadata, entityMetadataEntry, entityMetadataItem type 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:

  1. Set Entity Metadata (Play CB) — differential update for an already-spawned entity. Only the changed indices are sent.
  2. Spawn packets (historical, ≤ 1.8) — the full metadata list was embedded in Spawn Mob and Spawn 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:3438; minecraft-data data/pc/1.9/protocol.json (and all later versions).

2.1 Frame structure

EntityMetadata = (Entry)* Terminator

Entry = {
    Index  : u8     -- unsigned byte; 0x000xFE 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 Type VarInt is always small (≤ 2 bytes on the wire) but must be read as a full VarInt.

Note on minecraft-data 1.91.12.2: minecraft-data records the type field as i8 (signed byte) in those versions rather than varint. This is an artifact of minecraft-data's protocol description — since values 012 all fit in one byte, both readings are wire-compatible. ViaVersion's ModernEntityDataType reads it as VarInt from 1.9 onward, and the Minecraft wiki specifies VarInt. The varint encoding is authoritative; i8 in minecraft-data is a pragmatic approximation. The minecraft-data source confirms the change to varint in 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 3363) + 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.91.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.91.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 (030) 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.91.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.131.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.91.12.2; explicitly varint in 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 513 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 015): 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 1922:

  • 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_9 through EntityDataTypes1_19_3 have only optionalBlockStateType (never a separate blockStateType); EntityDataTypes1_19_4.java:45 is the first to add blockStateType = add(14, Types.VAR_INT) alongside optionalBlockStateType = add(15, Types.VAR_INT). Source: grep across EntityDataTypes1_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_TAG in 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 registryEntryHolder shape from 1.21.2 onward.

Full 1.21.x registry: see §3 above (31 entries, IDs 030).


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 1618); Position bit-layout changed ([x:26][z:26][y:12])
1.19 Cat Variant, Frog Variant, Optional Global Position, Painting Variant added (IDs 1922); 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:3650; 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 031 (5 bits) and type to 07 (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 i32 fields, 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 both blockStateType (14) and optionalBlockStateType (15). See §4.6.
  • optional_global_pos wire encoding: confirmed — GlobalBlockPositionType.java reads String dimension (the dimension ResourceLocation, e.g. minecraft:overworld) + BLOCK_POSITION1_14 (packed i64 position). The minecraft-data ['option', 'string'] representation is a schema approximation; the actual wire format is bool + (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, Digging added for 1.171.20 mobs) — not enumerated here; the authoritative list is at minecraft.wiki/w/Entity_format#Pose.