Files
minecraft_protocol/versions/1.16.md
T
claude-timemachine a3d5f64ef5 verify pass: resolve VERIFY flags (corrections + citations + honest UNCONFIRMED)
Corrected real errors: several 1.7.x release dates, resource_pack_send version,
config packet ordering, structured-component count (56), PLAYER_LOADED (1.21.4),
entity_sound_effect field order. Confirmed+cited the rest; remaining ~19 items
re-marked UNCONFIRMED (third-party/ViaLegacy/26.2 internals unreachable from refs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:03:44 +02:00

19 KiB
Raw Blame History

Java Edition 1.16 — Nether Update

Release Protocol Release Date Data Version
1.16 735 2020-06-23 2566
1.16.1 736 2020-06-24 2567
1.16.2 751 2020-08-11 2578
1.16.3 753 2020-09-10 2580
1.16.4 754 2020-11-02 2584
1.16.5 754 2021-01-15 2586

Sources: minecraft.wiki/w/Java_Edition_1.16 (fetched 2026-06-19), minecraft.wiki/w/Java_Edition_1.16.2 (fetched 2026-06-19), minecraft.wiki/w/Java_Edition_1.16.4 (fetched 2026-06-19); /tmp/mcproto-refs/minecraft-data/data/pc/*/version.json; ViaVersion/api/.../ProtocolVersion.java:7175.


Headline Changes (Nether Update)

1.16 overhauled the Nether completely: four new biomes (Crimson Forest, Warped Forest, Soul Sand Valley, Basalt Deltas), four new mobs (Piglin, Hoglin, Zoglin, Strider), and the Netherite equipment tier. The most consequential protocol change was replacing the dimension system's hardcoded int enum with a Codec NBT tag sent at login — the server now declares all available dimensions at runtime, enabling data-pack-defined custom dimensions. The chat text-component format gained full RGB hex colour support ("color":"#rrggbb"), and the Login Success packet changed the UUID wire type from a plain String to a proper binary UUID.


Protocol Changes vs 1.15.2

Login state

Login Success (0x02 C→client)

In 1.15.2 the uuid field was STRING — a plain text UUID like "550e8400-e29b-41d4-a716-446655440000".

In 1.16 it became a binary UUID (two Longs, big-endian, 16 bytes). ViaVersion translates the type on downgrade by parsing the string with UUID.fromString() and writing the binary form.

Source: Protocol1_15_2To1_16.java:8387 (ViaVersion) — UUID uuid = UUID.fromString(wrapper.read(Types.STRING)); wrapper.write(Types.UUID, uuid);; minecraft-data data/pc/1.15.2/protocol.json (uuid type: string) vs data/pc/1.16/protocol.json (uuid type: UUID).

Also: ViaVersion introduced ClientboundBaseProtocol1_16.java which overrides passthroughUUID to use Types.UUID instead of a string form, affecting all base login handling from 1.16 onwards.

Play state — clientbound

0x25 Login (Join Game) — major restructure

The Login/Join Game packet grew substantially. Diff against 1.15.2:

Field 1.15.2 1.16
entityId Int Int
gameMode UnsignedByte UnsignedByte
previousGameMode (absent) Byte (1 = none)
worldNames (absent) Array<String> — list of all registered world identifiers
dimensionCodec (absent) NBT CompoundTag — full registry of dimension types and biomes
dimension Int (1/0/1 enum) String resource key ("minecraft:overworld" etc.)
worldName (absent) String — current world identifier
hashedSeed Long Long
maxPlayers UnsignedByte UnsignedByte
levelType String (removed)
viewDistance VarInt VarInt
reducedDebugInfo Boolean Boolean
enableRespawnScreen Boolean Boolean
isDebug (absent) Boolean
isFlat (absent) Boolean

The dimensionCodec NBT tag has the key "dimension" mapping to a ListTag<CompoundTag>. Each dimension entry contains fields such as piglin_safe, natural, ambient_light, infiniburn, respawn_anchor_works, has_skylight, bed_works, has_raids, logical_height, shrunk, ultrawarm, has_ceiling, and optionally fixed_time. ViaVersion synthesizes this tag when downgrading from 1.16 to 1.15.2 via DimensionRegistries1_16.java (four entries: overworld, overworld_caves, the_nether, the_end).

Sources: EntityPacketRewriter1_16.java:133157 (ViaVersion); minecraft-data/data/pc/1.16/protocol.json (packet_login); DimensionRegistries1_16.java (full NBT structure).

0x3A Respawn — dimension field changed

Field 1.15.2 1.16
dimension Int (1/0/1 enum) String (resource key)
worldName (absent) String
hashedSeed Long Long
gamemode UnsignedByte UnsignedByte
previousGamemode (absent) Byte
levelType String (removed)
isDebug (absent) Boolean
isFlat (absent) Boolean
copyMetadata (absent) Boolean (keep player attributes on respawn)

Source: EntityPacketRewriter1_16.java:113131; minecraft-data/data/pc/1.16/protocol.json (packet_respawn vs 1.15.2).

0x0E Chat (clientbound) — sender UUID added

The Chat packet in 1.16 gained a sender UUID field (after the existing message string and position byte). ViaVersion fills it with the nil UUID 00000000-0000-0000-0000-000000000000 for system messages when downgrading.

Source: Protocol1_15_2To1_16.java:125135; minecraft-data/data/pc/1.16/protocol.json (packet_chat sender field type: UUID).

0x00 Spawn Entity — absorbed lightning bolt (ADD_GLOBAL_ENTITY removed)

packet_spawn_entity_weather (0x2C in 1.15.2) was removed. Lightning bolts are now spawned via the regular Spawn Entity (0x00) packet with an entity type of minecraft:lightning_bolt. ViaVersion handles this by converting the old ADD_GLOBAL_ENTITY packet (type byte 1 = lightning) to ADD_ENTITY with a synthesised UUID and zero velocity.

Source: EntityPacketRewriter1_16.java:85108; minecraft-data packet list diff (packet_spawn_entity_weather absent in 1.16).

Light Update (0x24) — new boolean field

A new Boolean field "take neighbour's light into account" was added to the Light Update packet. ViaVersion writes true for this field when downgrading.

Source: WorldPacketRewriter1_16.java:5965.

Chunk data — heightmap padding changed

The heightmap LongArray encoding in Chunk Data changed: 1.15 used compact arrays without per-element padding (values could span two longs), but 1.16 added per-element padding (each value is contained within a single long, with padding bits at the end of each long). ViaVersion re-packs the heightmap when downgrading.

Source: WorldPacketRewriter1_16.java:4657.

Block entities — UUID fields changed from String to int[]

Two block-entity NBT format changes:

  • minecraft:conduit: target_uuid (String) → Target (IntArray of 4 ints)
  • minecraft:skull: Owner.Id (String) → Owner.Id (IntArray); key Owner renamed to SkullOwner

Source: WorldPacketRewriter1_16.java:76118.

Play state — serverbound

0x0F Generate Structure (new)

New packet for generating Jigsaw structures in-game. Fields: location (BlockPosition), levels (VarInt), keepJigsaws (Boolean). ViaVersion cancels it when downgrading to 1.15.2.

Source: minecraft-data/data/pc/1.16/protocol.json (packet_generate_structure); Protocol1_15_2To1_16.java:202203.

0x05 Client Settings — new fields

CLIENT_INFORMATION in 1.16 added fields (specifically mainHand); ViaVersion cancels JIGSAW_GENERATE and SET_JIGSAW_BLOCK packets on downgrade.

0x0E Interact Entity — new sneak field

The Interact packet gained a trailing Boolean field indicating whether the player is sneaking. ViaVersion reads and discards it on downgrade.

Source: Protocol1_15_2To1_16.java:137152.

0x1A Player Abilities (serverbound) — fields removed

In 1.16 the client no longer sends flyingSpeed and walkingSpeed in the Player Abilities packet — only the flags byte remains. ViaVersion re-adds them from a PlayerAbilitiesProvider when downgrading.

Source: Protocol1_15_2To1_16.java:194199.

Status state

Status Response — player-sample name line-break no longer allowed. Line-break characters in player sample names are split into separate entries.

Source: Protocol1_15_2To1_16.java:89122.

Chat component — RGB hex colour support

In snapshot 20w17a (part of the 1.16 development cycle) the text-component "color" field was extended to accept 24-bit RGB hex strings of the form "#rrggbb" in addition to the 16 named colours ("red", "blue", etc.). This is a JSON-level change; no wire-format packet change accompanies it — the component is still serialised as a JSON string in the Chat packet. Older clients that don't understand "#rrggbb" will ignore or error on the colour.

Source: minecraft.wiki/w/Raw_JSON_text_format (fetched 2026-06-19); ComponentRewriter1_16.java processes component text but does not translate RGB colours, confirming ViaVersion does not downgrade them. Confirmed: ComponentRewriter1_16.java contains no hex-colour or RGB mapping logic; neither does its parent JsonNBTComponentRewriter.java — the "color" field is passed through unmodified on downgrade.

Attribute identifiers renamed

All attribute keys were renamed from camelCase to snake_case namespaced identifiers (e.g. generic.maxHealthminecraft:generic.max_health). ViaVersion maps these in AttributeMappings1_16.java and UPDATE_ATTRIBUTES rewriter.

Source: EntityPacketRewriter1_16.java:159197; AttributeMappings1_16.java.


Per-Patch Sub-sections

1.16 — Protocol 735 (2020-06-23)

Initial release of the Nether Update. All changes above (dimension codec, RGB, sender UUID, lightning bolt, heightmap padding) introduced here. The release was reuploaded ~6 hours after initial publication to address a Realms connectivity regression (MC-191138), but the hotfix failed and 1.16.1 followed the next day.

Source: minecraft.wiki/w/Java_Edition_1.16.

1.16.1 — Protocol 736 (2020-06-24)

Hotfix release. No protocol-level packet changes. The protocol number incremented from 735→736, making 1.16 and 1.16.1 mutually incompatible even though no packet structure changed. ViaVersion's Protocol1_16To1_16_1 is an essentially empty protocol class that registers no packet remappers (inherits the same ClientboundPackets1_16/ServerboundPackets1_16 enums for both sides).

Fixes: Slime block arrow launch (MC-110792), crossbow third-person animation (MC-146743), Endermen hostility (MC-186546), Realms crash (MC-191138).

Source: minecraft.wiki/w/Java_Edition_1.16.1; Protocol1_16To1_16_1.java (empty class body).

1.16.2 — Protocol 751 (2020-08-11)

Significant protocol update despite sharing the same major version. Key changes:

Join Game — isHardcore extracted + dimension field type changed

The isHardcore flag was separated from the gameMode byte (previously hardcoded as bit 0x08 of the gamemode value) into its own Boolean field. maxPlayers also changed from UnsignedByte to VarInt. Most critically, the dimension field changed again:

Field 1.16 1.16.2
isHardcore (packed in gameMode byte) Boolean (separate)
gameMode UnsignedByte (incl. hardcore bit) Byte (without hardcore bit)
dimensionCodec NBT (registry) NBT (registry, updated format)
dimension String (resource key) NBT CompoundTag (full dimension data inline)
maxPlayers UnsignedByte VarInt

In 1.16.2 the current dimension's full data is sent inline in the dimension field (a CompoundTag) rather than just a resource-key string referencing the registry. The dimensionCodec NBT registry was also updated.

ViaVersion converts from 1.16.2 back to 1.16 by reading the NBT dimension data from the registry mapping (MappingData1_16_2.getDimensionDataMap()), discarding the 1.16.2-style NBT, and writing the resource-key string.

Source: EntityPacketRewriter1_16_2.java:4170; minecraft-data/data/pc/1.16.2/protocol.json (packet_login); WorldPacketRewriter1_16_2.java.

Respawn — dimension field also became NBT

The dimension field in Respawn changed from String to CompoundTag, parallel to Login.

Source: EntityPacketRewriter1_16_2.java:7379.

Section Blocks Update (0x3B) — replaces Multi Block Change (0x0F)

multi_block_change was replaced by section_blocks_update and moved from packet ID 0x0F to 0x3B, causing all packets from 0x10 through 0x3A in 1.16 to shift down by one slot (0x0F0x3A in 1.16.2).

Old format (1.16): chunkX (Int) + chunkZ (Int) + array of records per block {horizontalPos:u8, y:u8, blockId:VarInt}.

New format (1.16.2): A single packed Long encoding chunkX:22 | chunkZ:22 | chunkY:20 (the chunk section rather than chunk column), a Boolean notTrustEdges, then an array of packed VarLongs each encoding blockStateId:52 | localX:4 | localZ:4 | localY:4. This is more efficient and per-section rather than per-column.

ViaVersion converts by splitting the old per-column records into per-section SECTION_BLOCKS_UPDATE packets.

Source: WorldPacketRewriter1_16_2.java:3974; ClientboundPackets1_16_2.java:83 (SECTION_BLOCKS_UPDATE at 0x3B); minecraft-data/data/pc/1.16.2/protocol.json (packet_section_blocks_update vs packet_multi_block_change).

Recipe Book — split into two serverbound packets

crafting_book_data (0x1E) was split into two packets:

  • RECIPE_BOOK_CHANGE_SETTINGS (0x1E) — book type, open state, filter state
  • RECIPE_BOOK_SEEN_RECIPE (0x1F) — single recipe identifier

All serverbound packets at 0x1F and above shifted up by one slot.

Source: Protocol1_16_1To1_16_2.java:5978; ServerboundPackets1_16_2.java:5455.

Entity type additions

New entity type Piglin Brute added. Piglin entity metadata indices 15 and 16 swapped between 1.16 and 1.16.2.

Source: EntityPacketRewriter1_16_2.java:8693.

Tags — furnace_materials removed

The block/item tag minecraft:furnace_materials was removed.

Source: Protocol1_16_1To1_16_2.java:85.

1.16.3 — Protocol 753 (2020-09-10)

Minimal release. The wiki states "the only difference between the two [1.16.3-rc1 and 1.16.3] is a change in the data and protocol versions." No packet-structure changes. ViaVersion's Protocol1_16_2To1_16_3 is an empty class that registers no remappers.

Fixes: Nether mob pathfinding, baby piglin item duplication.

Source: minecraft.wiki/w/Java_Edition_1.16.3; Protocol1_16_2To1_16_3.java (empty class body).

1.16.4 — Protocol 754 (2020-11-02)

Edit Book serverbound — hand field changed

The Edit Book serverbound packet (0x0C/0x1E depending on version) changed how the hand is encoded. In 1.16.4 the hand is now a VarInt slot index (40 = offhand, otherwise mainhand), whereas before it was a hand enum VarInt (0=main, 1=off). ViaVersion converts slot 40 → hand 1 (offhand), everything else → 0 (mainhand).

Source: Protocol1_16_3To1_16_4.java:3344.

Social Interactions screen

A new in-game social interactions screen was added (opens with P by default) allowing players to suppress chat from specific players. This is a client-side UI feature with no new protocol packets; it operates on the existing Chat packet sender UUID field (added in 1.16).

Source: minecraft.wiki/w/Java_Edition_1.16.4.

Snapshot high-bit scheme introduced

Starting with 1.16.4-pre1, Minecraft development releases began using protocol version numbers with bit 30 set (1 << 30 = 0x40000000). The release version of a snapshot's corresponding full release keeps the same protocol number as the previous compatible release in cases of wire compatibility.

For example, snapshots leading to 1.16.4 used versions like 0x40000001, 0x40000002, etc. (incrementing by 1 per snapshot), while the full 1.16.4 release used 754. The ViaVersion implementation encodes this as (1 << 30) | snapshotVersion in ProtocolVersion.java:305.

This convention has been used for all snapshot/pre-release versions ever since.

Source: minecraft.wiki/w/Java_Edition_1.16.4 ("New network protocol scheme, with a high bit (bit 30) set for snapshots"); ProtocolVersion.java:305 (return (1 << 30) | snapshotVersion); minecraft.wiki/w/Protocol_version (fetched 2026-06-19).

1.16.5 — Protocol 754 (2021-01-15)

Shares protocol 754 with 1.16.4; 1.16.4 clients can connect to 1.16.5 servers and vice versa. The update addressed server crash bugs (MC-203337: IllegalStateException in POI data). No protocol-level changes; ViaVersion has no separate protocol class for 1.16.5 and registers it as a sub-version of 754.

Source: minecraft.wiki/w/Java_Edition_1.16.5; ProtocolVersion.java:75 (register(754, "1.16.4-1.16.5", new SubVersionRange("1.16", 4, 5))).


Summary: Packet ID Renumbering in 1.16.2

The removal of multi_block_change from slot 0x0F and its replacement with section_blocks_update at 0x3B meant that all 1.16 clientbound play packets between 0x10 and 0x3A shifted down by one ID in 1.16.2. Any hardcoded packet ID table for 1.16 is incorrect for 1.16.2.

Notable shifts (1.16 ID → 1.16.2 ID):

Packet 1.16 1.16.2
multi_block_change 0x0F (removed / renamed to section_blocks_update at 0x3B)
tab_complete 0x10 0x0F
declare_commands 0x11 0x10
login (Join Game) 0x25 0x24
respawn 0x3A 0x39
section_blocks_update (absent) 0x3B

Source: minecraft-data/data/pc/1.16/protocol.json vs data/pc/1.16.2/protocol.json packet ID mappings; ClientboundPackets1_16.java vs ClientboundPackets1_16_2.java.


Proxy / Translation Impact

Dimension codec — proxies must maintain per-session registry

Before 1.16, proxies could route players between servers without caring about dimension state in the Join Game packet (dimension was a fixed enum). From 1.16 onwards:

  1. The proxy must read the dimensionCodec NBT from every backend's Login response and cache it per player.
  2. On dimension change (Respawn packet), the proxy must update its cached notion of the current dimension.
  3. If a ViaVersion-managed proxy downgrades a 1.16 client to a pre-1.16 backend, it must synthesise the dimension codec from scratch (using DimensionRegistries1_16).
  4. In 1.16.2 the current dimension's NBT data is sent inline in both Login and Respawn; the proxy must parse and relay that tag accurately or player loading breaks.

Login Success UUID type change

Pre-1.16 proxies that parse Login Success as a plain string UUID will misread the packet after 1.16. The 16-byte binary UUID is not a valid UTF-8 string; the proxy must switch UUID handling based on protocol version (≥735 = binary UUID).

Section Blocks Update (1.16.2+) — packet format incompatibility

The new section-relative, VarLong-packed format of section_blocks_update is completely incompatible with the old multi_block_change format. Any protocol-translation layer must reconstruct the records from scratch when crossing the 1.16/1.16.2 boundary.

Snapshot high-bit (1.16.4+ clients)

Development clients connecting to proxies send protocol versions with bit 30 set. A proxy that expects protocol versions ≤ 1000 will misidentify a snapshot client as an unknown/future version. Proxies must mask out bit 30 to identify the base release version, then handle snapshot clients as the corresponding release version or reject them gracefully.