Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
19 KiB
06 — Configuration State
Version gate: the Configuration state does not exist before 1.20.2 (protocol 764). Nothing in this document applies to 1.20.1 or earlier. Known Packs (§4) requires 1.20.5 (protocol 766) or later.
1. Why it was added
Before 1.20.2, the Join Game / Login (play) packet was the kitchen sink: it carried
dimension codec NBT, dimension type, feature flags, and more. Tags arrived as a separate
Update Tags play packet immediately after. This meant:
- Registry data (codec NBT) had to be resent in full every time the client changed servers or re-entered a world.
- There was no clean hook for sending resource packs or feature flags before the client started rendering the world.
- Server-to-server transfers (dimension changes, datapack reloads) required hacky re-use of the play state with no protocol-level guarantee the client was in a clean state.
1.20.2 introduced a dedicated Configuration phase that sits between Login and Play.
The server can keep the client in Configuration as long as needed, push all setup data
(registries, resource packs, feature flags, tags), then release it into Play. Crucially,
the server can pull the client back from Play into Configuration at any time
(Start Configuration → Finish Configuration handshake), enabling clean
dimension/datapack changes without a full disconnect.
Source: minecraft.wiki Java Edition protocol (Configuration) [primary]; node-minecraft-protocol src/client/play.js:39-68; Velocity ConfigSessionHandler.java, ClientConfigSessionHandler.java.
2. Connection-state machine
HANDSHAKING ──► STATUS (ping/list)
└──► LOGIN
│
│ Login Success (CB 0x02)
│ Login Acknowledged (SB 0x03, added 1.20.2)
▼
CONFIGURATION ◄────────────────────────────────┐
│ │
│ Finish Configuration (CB 0x03) │
│ Ack Finish Configuration (SB 0x03) │
▼ │
PLAY ────── Start Configuration (CB) ───────┘
State enum source: node-minecraft-protocol/src/states.js:3-9 — defines HANDSHAKING, STATUS, LOGIN, CONFIGURATION, PLAY.
Transition packet source: Velocity/StateRegistry.java:853-854 — LoginAcknowledgedPacket registered at 0x03 from MINECRAFT_1_20_2.
3. Configuration flow
3.1 Initial entry (Login → Configuration)
sequenceDiagram
participant C as Client
participant S as Server
Note over C,S: LOGIN state
S->>C: Login Success (Login CB 0x02)
C->>S: Login Acknowledged (Login SB 0x03)
Note over C,S: CONFIGURATION state begins
C->>S: Plugin Message minecraft:brand (Config SB 0x02 / 0x01 pre-1.20.5)
Note right of S: client brand string e.g. "vanilla"
C->>S: Client Information (Config SB 0x00)
Note right of S: locale, render distance, chat mode, skin parts, main hand
alt 1.20.5 and later (protocol 766+)
S->>C: Known Packs (Config CB 0x0E)
C->>S: Known Packs (Config SB 0x07)
Note over C,S: server computes registry diff
end
S->>C: Feature Flags (Config CB 0x0C / 0x07 pre-1.20.5)
Note left of C: enabled experiment identifiers
S->>C: Registry Data x N (Config CB 0x07 / 0x05 pre-1.20.5)
Note left of C: one packet per registry; delta if Known Packs matched
S->>C: Update Tags (Config CB 0x0D / 0x08 pre-1.20.5)
opt Resource packs
S->>C: Add Resource Pack (Config CB 0x09)
C->>S: Resource Pack Response (Config SB 0x06)
end
S->>C: Finish Configuration (Config CB 0x03)
C->>S: Acknowledge Finish Configuration (Config SB 0x03)
Note over C,S: PLAY state begins
S->>C: Login Play (Play CB 0x29 in 1.20.2)
The server controls ordering within Configuration. The sequence above reflects
Vanilla ordering per minecraft.wiki/w/Java_Edition_protocol/FAQ (steps 11–20). The client must not transition to Play until it
receives Finish Configuration. Note: brand (step 11) is sent before Client Information (step 12) per the wiki; the server does not enforce this ordering.
Source: minecraft.wiki/w/Java_Edition_protocol/FAQ steps 11–20 (authoritative ordering); node-minecraft-protocol/src/server/login.js:224-239 (server sends registry_data
then finish_configuration); node-minecraft-protocol/src/client/play.js:49-68 (client
handles select_known_packs → finish_configuration → state=PLAY).
3.2 Packet IDs at a glance (Configuration state)
Packet IDs shifted at 1.20.5 when Cookie Request/Response, Store Cookie, and
Transfer were inserted. The table below shows the two main stable points. For 1.21.x+
IDs see StateRegistry.java:163-261 directly.
Clientbound (server → client)
| Packet name | 1.20.2–1.20.4 | 1.20.5+ | Official name |
|---|---|---|---|
| Cookie Request | — | 0x00 | cookie_request |
| Plugin Message | 0x00 | 0x01 | custom_payload |
| Disconnect | 0x01 | 0x02 | disconnect |
| Finish Configuration | 0x02 | 0x03 | finish_configuration |
| Keep Alive | 0x03 | 0x04 | keep_alive |
| Ping | 0x04 | 0x05 | ping |
| Reset Chat | — | 0x06 | reset_chat |
| Registry Data | 0x05 | 0x07 | registry_data |
| Remove Resource Pack | 0x06 (1.20.3) | 0x08 | resource_pack_pop |
| Add Resource Pack | 0x06 | 0x09 | resource_pack_push |
| Store Cookie | — | 0x0A | store_cookie |
| Transfer | — | 0x0B | transfer |
| Feature Flags | 0x07 | 0x0C | update_enabled_features |
| Update Tags | 0x08 | 0x0D | update_tags |
| Known Packs (CB) | — | 0x0E | select_known_packs |
Source: StateRegistry.java:202-261 (CONFIG clientbound block).
Serverbound (client → server)
| Packet name | 1.20.2–1.20.4 | 1.20.5+ | Official name |
|---|---|---|---|
| Client Information | 0x00 | 0x00 | client_information |
| Cookie Response | — | 0x01 | cookie_response |
| Plugin Message | 0x01 | 0x02 | custom_payload |
| Acknowledge Finish Configuration | 0x02 | 0x03 | finish_configuration |
| Keep Alive | 0x03 | 0x04 | keep_alive |
| Pong | 0x04 | 0x05 | pong |
| Resource Pack Response | 0x05 | 0x06 | resource_pack |
| Known Packs (SB) | — | 0x07 | select_known_packs |
Source: StateRegistry.java:165-200 (CONFIG serverbound block).
4. Known Packs handshake (1.20.5 / protocol 766)
Added in 1.20.5. Not present in 1.20.2–1.20.4.
Registry data can be large. If the client already has the vanilla datapack's registry entries (shipped with the client JAR), the server need not resend them. The Known Packs exchange lets server and client negotiate which packs each side knows, so Registry Data only sends the diff.
sequenceDiagram
participant C as Client
participant S as Server
Note over C,S: CONFIGURATION state (1.20.5+)
S->>C: Known Packs CB (0x0E) — server's list of packs
C->>S: Known Packs SB (0x07) — subset client already has locally
Note over C,S: server computes intersection
S->>C: Registry Data — only entries NOT covered by client's known packs
Wire format — each KnownPack entry is three length-prefixed strings read
sequentially: namespace, id, version. VarInt count prefix. Serverbound capped at 64
entries by default.
Source: KnownPacksPacket.java:68-78 (full record and read/write methods).
// KnownPacksPacket.java:68
public record KnownPack(String namespace, String id, String version) {
private static KnownPack read(ByteBuf buf) {
return new KnownPack(
ProtocolUtils.readString(buf),
ProtocolUtils.readString(buf),
ProtocolUtils.readString(buf));
}
How Velocity uses the Known Packs boundary
ClientConfigSessionHandler.callConfigurationEvent() javadoc (lines 305-320):
"For 1.20.5+ backends this is done when the client responds to the known packs request. The response is delayed until the event has been called. For 1.20.2–1.20.4 servers this is done when the client acknowledges the end of the configuration. This is handled differently because for 1.20.5+ servers can't keep their connection alive between states and older servers don't have the known packs transaction."
ClientConfigSessionHandler.handle(KnownPacksPacket) (line 176) fires
PlayerConfigurationEvent then forwards the client's response to the backend.
node-minecraft-protocol/src/client/play.js:56-58:
client.once('select_known_packs', () => {
client.write('select_known_packs', { packs: [] })
// sends empty list → server sends full registry
})
5. Re-configuration: Play → Configuration loop
The server can return the client to Configuration from Play at any time. Vanilla uses
this for dimension changes, datapack reloads (/reload), and server transfers (1.20.5+).
sequenceDiagram
participant C as Client
participant S as Server
Note over C,S: PLAY state (mid-session)
S->>C: Start Configuration (Play CB — see ID table below)
C->>S: Acknowledge Configuration (Play SB 0x0B in 1.20.2)
Note over C,S: CONFIGURATION state (re-entry)
S->>C: Known Packs CB (1.20.5+)
C->>S: Known Packs SB (1.20.5+)
S->>C: Registry Data / Feature Flags / Update Tags / Resource Packs as needed
S->>C: Finish Configuration (Config CB 0x03)
C->>S: Acknowledge Finish Configuration (Config SB 0x03)
Note over C,S: PLAY state resumes
S->>C: Login Play or Respawn
Start Configuration Play-state clientbound packet IDs:
| Version range | ID |
|---|---|
| 1.20.2 | 0x65 |
| 1.20.3–1.20.4 | 0x67 |
| 1.20.5–1.21.1 | 0x69 |
| 1.21.2–1.21.4 | 0x70 |
| 1.21.5–1.21.8 | 0x6F |
Source: StateRegistry.java:804-813 (PLAY clientbound StartUpdatePacket mappings).
StartUpdatePacket has zero fields — it is a signal-only packet.
Source: StartUpdatePacket.java:35-38 — empty decode/encode bodies.
node-minecraft-protocol re-entry code (src/client/play.js:42-54):
// Server can tell client to re-enter config state
client.on('start_configuration', () => enterConfigState())
function enterConfigState(finishCb) {
if (client.state === states.CONFIGURATION) return
// If we are returning from the play state, acknowledge it
if (client.state === states.PLAY) {
client.write('configuration_acknowledged', {})
}
client.state = states.CONFIGURATION
// ...
}
Players in Configuration are not visible on the tab list.
How Velocity bridges re-configuration
Two session handler classes manage each side:
-
ConfigSessionHandler(backend side,connection/backend/) — forwardsRegistrySyncPacket,TagsUpdatePacket,ActiveFeaturesPacketdirectly to the client. InterceptsFinishedUpdatePacketto orchestrate the pipeline switch. -
ClientConfigSessionHandler(player side,connection/client/) — handles packets from the client during Configuration.handle(FinishedUpdatePacket)(line 118) switches the client connection's active handler toClientPlaySessionHandlerand completesconfigSwitchFuture.
The decoder pipeline is explicitly rewound to StateRegistry.PLAY on the backend side
before writing FinishedUpdatePacket back, ensuring the Netty decoder expects
Play-state packets from that point:
// ConfigSessionHandler.java:240-241 — handle(FinishedUpdatePacket)
smc.getChannel().pipeline().get(MinecraftVarintFrameDecoder.class).setState(StateRegistry.PLAY);
smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.PLAY);
6. What moved out of Join Game / Login Play
Before 1.20.2, Join Game (Play CB) carried a registryCodec NBT compound containing
all dimension types, biome registries, damage types, etc. In 1.20.2+ the field is
absent from the Join Game packet entirely.
RegistrySyncPacket.java:34 notes:
// NBT change in 1.20.2 makes it difficult to parse this packet.
Velocity treats it as an opaque byte slice and forwards verbatim.
node-minecraft-protocol shows the split (src/server/login.js:226-233):
if (client.supportFeature('segmentedRegistryCodecData')) {
for (const key in options.registryCodec) {
client.write('registry_data', entry) // one packet per registry
}
} else {
client.write('registry_data', { codec: options.registryCodec || {} }) // pre-1.20.5 monolithic
}
Confirmed 1.20.5: minecraft-data/data/pc/common/features.json sets segmentedRegistryCodecData -> ['1.20.5', 'latest']. Verified against protocol.json: 1.20.2 and 1.20.3 use monolithic codec: anonymousNbt; 1.20.5 uses id: string + entries: [] (per-registry). Not 1.20.3.
7. Notable edge cases
Resource pack state across re-configuration — Velocity 1.20.2 clears applied resource
packs on ConfigSessionHandler.activated() and re-queues the first previously-applied
pack after FinishedUpdatePacket to avoid double-applying.
Source: ConfigSessionHandler.java:activated() and handle(FinishedUpdatePacket):253.
Brand forwarding — the client sends minecraft:brand immediately after Login Acknowledged
(before the backend may be ready). Velocity caches the brand string; on
handleBackendFinishUpdate it re-writes a brand Plugin Message to the backend before
firing finish-config events.
Source: ClientConfigSessionHandler.java:handleBackendFinishUpdate() (line 330-337).
PlayerConfigurationEvent timing differs by version — 1.20.5+ fires when
KnownPacksPacket is received; 1.20.2–1.20.4 fires when Acknowledge Finish Configuration
arrives. The comment in callConfigurationEvent() (lines 305-320) explains the reason.
Keep Alive in Configuration — Keep Alive packets are valid in Configuration state
(same packet, same format). The client can be held in Configuration indefinitely;
Keep Alive prevents timeout.
Source: StateRegistry.java:179-185 — KeepAlivePacket registered at 0x03 (1.20.2)
and 0x04 (1.20.5+) in the CONFIG serverbound block.
8. Resolution of prior VERIFY flags
Packet ordering (Client Information vs brand): CORRECTED. minecraft.wiki/w/Java_Edition_protocol/FAQ steps 11–12: client sends brand (step 11) before Client Information (step 12). Sequence diagram above updated accordingly.
Update Tags during initial Configuration: CONFIRMED OPTIONAL. Wiki step 18 lists Update Tags as "(Optional)" during initial configuration. It is present in the clientbound packet table and Vanilla sends it, but it is not mandatory.
Known Packs CB vs brand ordering: CONFIRMED. Wiki steps 13 (server brand) and 15 (Known Packs CB): server sends its brand first, then Feature Flags (step 14), then Known Packs CB (step 15). Sequence diagram above already reflects this correctly (Known Packs inside the 1.20.5+ alt block, after server brand).
segmentedRegistryCodecData version: CONFIRMED 1.20.5. minecraft-data/data/pc/common/features.json: segmentedRegistryCodecData -> ['1.20.5', 'latest']. Verified: 1.20.2/1.20.3 protocol.json shows monolithic codec: anonymousNbt; 1.20.5 shows id: string + entries: array (per-registry). Source: node-minecraft-protocol/src/server/login.js:226 (if (client.supportFeature('segmentedRegistryCodecData'))).
Players hidden from tab list during mid-session re-configuration: UNCONFIRMED (Vanilla server behaviour). Velocity ClientPlaySessionHandler.java:607 calls player.getTabList().clearAllSilent() on entering Configuration, clearing the proxy's local tab-list state without sending a packet to other clients. Whether Vanilla removes the re-configuring player from other clients' tab lists is not documented on minecraft.wiki and is not observable from proxy source alone. The proxy-side behaviour is confirmed; Vanilla-side is not.
Sources
| Reference | Used for |
|---|---|
| minecraft.wiki/w/Java_Edition_protocol/Packets | Packet tables, IDs, official names (fetched 2026-06-19) |
Velocity/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java |
Authoritative packet ID mapping for all versions (lines 163-261 CONFIG block; 804-813 StartUpdatePacket) |
Velocity/…/connection/backend/ConfigSessionHandler.java |
Backend-side Configuration handling; pipeline switch; resource pack state |
Velocity/…/connection/client/ClientConfigSessionHandler.java |
Client-side Configuration handling; Known Packs event timing; brand forwarding |
Velocity/…/protocol/packet/config/KnownPacksPacket.java |
Known Packs wire format — namespace/id/version record |
Velocity/…/protocol/packet/config/RegistrySyncPacket.java |
Opaque-forward note; NBT change comment |
Velocity/…/protocol/packet/config/StartUpdatePacket.java |
Zero-length signal packet |
Velocity/…/protocol/packet/config/ActiveFeaturesPacket.java |
Feature flags as Key[] array |
node-minecraft-protocol/src/states.js:3-9 |
State enum: CONFIGURATION alongside HANDSHAKING/STATUS/LOGIN/PLAY |
node-minecraft-protocol/src/server/login.js:189-239 |
Server-side: Login Acknowledged handler → Configuration state; registry_data loop |
node-minecraft-protocol/src/client/play.js:32-68 |
Client-side: Login Success → Configuration entry; re-entry from Play; select_known_packs response |