a3d5f64ef5
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>
315 lines
19 KiB
Markdown
315 lines
19 KiB
Markdown
# 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 `Long`s, 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); key `Owner` renamed to `SkullOwner`
|
||
|
||
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 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: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:
|
||
|
||
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.
|