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>
19 KiB
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:71–75.
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:83–87 (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:133–157 (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:113–131; 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:125–135; 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:85–108; 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:59–65.
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:46–57.
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); keyOwnerrenamed toSkullOwner
Source: WorldPacketRewriter1_16.java:76–118.
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:202–203.
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:137–152.
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:194–199.
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:89–122.
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.maxHealth → minecraft:generic.max_health). ViaVersion maps these in AttributeMappings1_16.java and UPDATE_ATTRIBUTES rewriter.
Source: EntityPacketRewriter1_16.java:159–197; 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:41–70; 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:73–79.
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 (0x0F–0x3A 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:39–74; 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 stateRECIPE_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:59–78; ServerboundPackets1_16_2.java:54–55.
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:86–93.
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:33–44.
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:
- The proxy must read the
dimensionCodecNBT from every backend's Login response and cache it per player. - On dimension change (Respawn packet), the proxy must update its cached notion of the current dimension.
- 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). - 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.