# Connection Lifecycle — Java Edition Protocol State Machine > **Scope**: 1.7.10 through current (26.1 / protocol 775). Version-specific divergences are called out inline. > **Connective-tissue doc**: per-phase detail lives in `03-handshake.md`, `04-status.md`, `05-login.md`, `06-configuration.md`, `07-play.md`. This doc covers the state machine, transition triggers, and the ordered exchange that takes a raw TCP socket to in-game. --- ## 1. States The protocol defines five named states. Each state carries its own packet-ID namespace (IDs are reused across states): | State | Integer ID | Direction of entry | Purpose | |---|---|---|---| | **Handshaking** | (implicit, initial) | TCP connect | One packet; chooses the next state | | **Status** | 1 | via Handshake intent=1 | Server-list ping/pong; no auth | | **Login** | 2 | via Handshake intent=2 (or 3) | Auth, encryption, compression, identity | | **Configuration** | — (no integer) | after Login Acknowledged (≥1.20.2) | Registry sync, feature flags, resource packs | | **Play** | — | after Finish Configuration (or Login Success <1.20.2) | Gameplay | Sources: - State names: `node-minecraft-protocol/src/states.js:3-9` - Velocity enum: `Velocity/proxy/…/StateRegistry.java:140-263` (`HANDSHAKE`, `STATUS`, `CONFIG`, `PLAY`, `LOGIN`) - Integer IDs (STATUS_ID=1, LOGIN_ID=2, TRANSFER_ID=3): `StateRegistry.java:881-883` - Intent=3 (Transfer login, ≥1.20.5): `minecraft.wiki/w/Java_Edition_protocol/Packets` --- ## 2. State Machine Diagram ``` TCP connect │ ▼ ┌──────────────┐ │ HANDSHAKING │ (one packet: set_protocol / Handshake 0x00) └──────┬───────┘ │ intent field ├─ 1 ──────────────────────────┐ │ │ ▼ ▼ ┌─────────┐ ┌─────────┐ │ STATUS │ │ LOGIN │ └────┬────┘ └────┬────┘ │ ping done / disconnect │ Login Success + LoginAcknowledged ▼ │ (≥1.20.2 only) [close] ┌───┴──────────────┐ │ CONFIGURATION │ (≥1.20.2 / protocol 764) └───┬──────────────┘ │ Finish Configuration │ ┌─────────────────────┐ ▼ ◄─────────────┤ Start Configuration │ ┌────────┐ │ (Play→Config re-cfg) │ │ PLAY ├────────────►┘ │ └────────┘ └─────────────────────┘ ``` **Pre-1.20.2 path**: Login Success → PLAY directly (no Configuration state, no Login Acknowledged). --- ## 3. Handshake: How next-state is chosen The very first packet sent on a new TCP connection is the serverbound **Handshake** (packet ID `0x00` in the HANDSHAKING state, stable since 1.7.2). Fields: - `protocolVersion` — VarInt, the client's protocol number - `serverHost` — String (max 255), the address the client typed - `serverPort` — Unsigned Short - `nextState` — VarInt enum: - `1` → STATUS - `2` → LOGIN - `3` → LOGIN after server transfer (≥1.20.5 / protocol 766). Source: Velocity `HandshakeIntent.java:16` (`TRANSFER(3)`), `StateRegistry.java:883` (`TRANSFER_ID = 3`), `ProtocolVersion.java:89` (`MINECRAFT_1_20_5(766, "1.20.5", "1.20.6")`); ViaVersion `InitialBaseProtocol.java:133` rejects intent=3 for servers older than v1_20_5. The server reads `nextState` and immediately switches its decoder to the corresponding state. There is no server response in the Handshaking state. Source: `node-minecraft-protocol/src/server/handshake.js:34-38` ```js if (packet.nextState === 1) { client.state = states.STATUS } else if (packet.nextState === 2) { client.state = states.LOGIN } ``` The client side always sends `nextState: 2` for normal login: `node-minecraft-protocol/src/client/setProtocol.js:21-27` ```js client.write('set_protocol', { protocolVersion: options.protocolVersion, serverHost: taggedHost, serverPort: options.port, nextState: 2 }) client.state = states.LOGIN ``` --- ## 4. Status Path (Server List Ping) Used by the client (and external tools) to read MOTD, player count, and favicon without authenticating. ```mermaid sequenceDiagram participant C as Client participant S as Server C->>S: "Handshake (0x00, nextState=1)" Note over C,S: Both switch to STATUS state C->>S: "Status Request (0x00)" S->>C: "Status Response (0x00) — JSON payload" C->>S: "Ping Request (0x01) — Long timestamp" S->>C: "Pong Response (0x01) — echo timestamp" Note over C,S: Server closes connection ``` Notes: - The Ping Request / Pong Response round-trip is optional but used by vanilla to measure latency. - No encryption, no compression, no auth in this path. - Server handler: `node-minecraft-protocol/src/server/ping.js:7-64` — writes `server_info` then echoes `ping` then ends. - Velocity STATUS registration: `StateRegistry.java:148-161` — `StatusRequestPacket` 0x00, `StatusPingPacket` 0x01 (both directions). --- ## 5. Login Path ### 5a. Pre-1.20.2 (protocol < 764): Login → Play directly ```mermaid sequenceDiagram participant C as Client participant S as Server C->>S: "Handshake (0x00, nextState=2)" Note over C,S: Both switch to LOGIN state C->>S: "Login Start (0x00) — username + UUID" Note over S: online-mode check S->>C: "Encryption Request (0x01) — serverId, publicKey, verifyToken [online-mode only]" Note over C: Mojang auth (joinServer call) C->>S: "Encryption Response (0x01) — encrypted sharedSecret + verifyToken" Note over C,S: Both enable AES-128-CFB8 encryption immediately Note over S: Yggdrasil hasJoined verification S->>C: "Set Compression (0x03) — threshold [optional, >=1.8]" Note over C,S: All subsequent packets use compressed format if enabled S->>C: "Login Success (0x02) — UUID + username + properties" Note over C,S: Both switch to PLAY state S->>C: "Join Game (Play 0x01/varies) — game state, dimension, etc." ``` Key sequencing rules (pre-1.20.2): - **Set Compression must precede Login Success.** Anything sent after Set Compression (including Login Success itself) uses the compressed packet format. Source: minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol - **Encryption activates immediately** after the server receives Encryption Response — both sides flip the AES cipher before the next byte. Source: `node-minecraft-protocol/src/client/encrypt.js:73` (`client.setEncryption(sharedSecret)` called before returning from handler); `node-minecraft-protocol/src/server/login.js:150` (server side, same). - Offline-mode servers skip Encryption Request/Response entirely (`node-minecraft-protocol/src/server/login.js:88-106`). - Set Compression was added in 1.8 (protocol 47). Prior versions (1.7.x) have no compression packet; per-packet compression existed in very early snapshots but was dropped. Source: `StateRegistry.java:869-870` (`SetCompressionPacket` mapped from `MINECRAFT_1_8`). ### 5b. 1.20.2+ (protocol ≥ 764): Login → Configuration → Play The big change in 1.20.2 inserted the **Configuration** state between Login and Play. The gate is `LoginAcknowledged` — the client must explicitly acknowledge Login Success before Configuration begins. ```mermaid sequenceDiagram participant C as Client participant S as Server C->>S: "Handshake (0x00, nextState=2)" Note over C,S: Both switch to LOGIN state C->>S: "Login Start (0x00) — username + UUID" S->>C: "Encryption Request (0x01) [online-mode only]" Note over C: Mojang auth C->>S: "Encryption Response (0x01)" Note over C,S: AES-128-CFB8 encryption enabled S->>C: "Set Compression (0x03) [optional]" Note over C,S: Compressed format enabled if threshold >= 0 S->>C: "Login Success (0x02) — UUID + username + properties" C->>S: "Login Acknowledged (0x03) [NEW in 1.20.2]" Note over C,S: Both switch to CONFIGURATION state S->>C: "Registry Data (0x05/0x07) — codec entries" S->>C: "Feature Flags (0x0C) — enabled experiments" S->>C: "Known Packs (0x0E) [>=1.20.5]" C->>S: "Known Packs (0x07) — client's pack list [>=1.20.5]" S->>C: "Tags (0x0D/varies) — tag registry" S->>C: "Finish Configuration (0x02/0x03)" C->>S: "Acknowledge Finish Configuration (0x02/0x03)" Note over C,S: Both switch to PLAY state S->>C: "Login / Join Game (Play 0x29/varies)" ``` Sources: - `LoginAcknowledgedPacket` registered serverbound at `0x03` from `MINECRAFT_1_20_2`: `StateRegistry.java:853-854` - Server side transition: `node-minecraft-protocol/src/server/login.js:189-239` — on `login_acknowledged`, server switches to `states.CONFIGURATION`, sends `registry_data`, then `finish_configuration`; client acks with `finish_configuration`, server switches to `states.PLAY` - Client side: `node-minecraft-protocol/src/client/play.js:40-43` — on `success`, writes `login_acknowledged` and calls `enterConfigState()` - `KnownPacksPacket` registered in CONFIG from `MINECRAFT_1_20_5`: `StateRegistry.java:193-194, 249-250` - Velocity CONFIG state registration starts at `MINECRAFT_1_20_2` throughout: `StateRegistry.java:163-261` --- ## 6. Configuration State **Introduced**: 1.20.2, protocol 764. Source: `StateRegistry.java:163` (first CONFIG packet mapped from `MINECRAFT_1_20_2`); minecraft.wiki/w/Java_Edition_protocol/Packets. Purpose: synchronise server-side data (registry codec, feature flags, resource packs, tags, known data packs) to the client before any gameplay packets flow. Replacing what was previously stuffed into the Login sequence or early Play packets. Key packets (clientbound unless noted): - `Plugin Message` — channel-based custom data, same as Play - `Disconnect` — kick during config - `Keep Alive` — connection liveness check - `Registry Data` — serialised registry codec entries (dimension types, biomes, damage types, etc.) - `Feature Flags` — experimental feature toggles - `Resource Pack Request` — push a resource pack - `Tags Update` — tag data (formerly in Play) - `Known Packs` (≥1.20.5) — bidirectional; server asks, client responds with list of known data packs to skip redundant sync - `Finish Configuration` (clientbound) → `Acknowledge Finish Configuration` (serverbound) — the handshake that exits Configuration Full CONFIG packet table: `StateRegistry.java:163-261` --- ## 7. Re-configuration: Play → Configuration **Since 1.20.2.** A connected player can be sent back to Configuration state without disconnecting. This allows servers to push updated registries (e.g., when switching between sub-servers with different dimensions). Trigger: server sends **Start Configuration** (`StartUpdatePacket`) in the Play state. - Velocity: `StateRegistry.java:805-813`, registered from `MINECRAFT_1_20_2` Client responds: **Acknowledge Configuration** (serverbound Play packet, Velocity name `FinishedUpdatePacket`) - Velocity: `StateRegistry.java:407-413`, registered from `MINECRAFT_1_20_2` Both sides switch back to Configuration state. The full Configuration exchange runs again (registry data, finish handshake). On `Acknowledge Finish Configuration`, both switch back to Play. Client-side handling in node-minecraft-protocol: ```js // node-minecraft-protocol/src/client/play.js:43 client.on('start_configuration', () => enterConfigState()) // ... // play.js:51-53: if state===PLAY, write 'configuration_acknowledged' before switching if (client.state === states.PLAY) { client.write('configuration_acknowledged', {}) } client.state = states.CONFIGURATION ``` Source: `node-minecraft-protocol/src/client/play.js:43-55` --- ## 8. Encryption Details - **Algorithm**: AES-128-CFB8, symmetric, both directions use the same 16-byte shared secret. - **Key exchange**: RSA (server's ephemeral keypair, ≥1024-bit); client encrypts shared secret and verify token with server's public key. - **Activation point**: immediately after Encryption Response is sent/received — before the next byte on the wire. Encryption applies to all subsequent data including Set Compression and Login Success. - **Online-mode only** (vanilla): offline-mode servers skip the Encryption Request/Response entirely. This is version-independent behaviour — node-minecraft-protocol gates the entire Encryption Request/Response exchange on `needToVerify` (`server/login.js:52,88`), with no protocol-version branch. The minecraft.wiki states "As of 1.21, the vanilla server never uses encryption in offline mode" — this is a documentation style phrasing (confirming current vanilla behaviour), not a description of a 1.21 protocol change. The behaviour has been stable since at least 1.8 and is unrelated to any specific protocol version. - Source: `node-minecraft-protocol/src/client/encrypt.js` (client), `node-minecraft-protocol/src/server/login.js:88-155` (server) --- ## 9. Compression Details - **Algorithm**: zlib deflate, wrapped in the compressed packet format (prepended with uncompressed data length as VarInt). - **Threshold**: packets with uncompressed size ≥ threshold are compressed; smaller packets are sent uncompressed (length VarInt = 0). - **Activation point**: Set Compression (Login 0x03) must arrive before Login Success. All subsequent packets — including Login Success — use the compressed format. Source: minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol - **Version gating**: added in 1.8 (protocol 47). Not present in 1.7.x. Source: `StateRegistry.java:869-870` - Default threshold used by node-minecraft-protocol: 256 bytes. Source: `node-minecraft-protocol/src/server/login.js:179` - Client receives `compress` / `set_compression` event: `node-minecraft-protocol/src/client/compress.js:3-8` --- ## 10. Protocol Version Reference | Minecraft version | Protocol number | Notable state-machine change | |---|---|---| | 1.7.2 | 4 | Baseline modern protocol; Handshake/Status/Login/Play | | 1.8 | 47 | Set Compression (0x03 in Login) added | | 1.13 | 393 | Login Plugin Message (0x04) added | | 1.20.2 | 764 | **Configuration state inserted**; Login Acknowledged; re-configuration | | 1.20.5 | 766 | Known Packs exchange in Configuration; Cookie packets; Transfer login intent=3 | | 1.21 | 767 | (Config/Play packet renumbering) | | 1.21.2 | 769 | (further renumbering) | | 26.1 | 775 | Current as of 2026-06 | Sources: Velocity `StateRegistry.java` import block (lines 21-50); minecraft.wiki/w/Java_Edition_protocol/Packets (protocol 773/775 noted). --- ## Resolution notes (formerly VERIFY flags) **intent=3 (Transfer) minimum version — CONFIRMED** ≥1.20.5 (protocol 766). Sources: Velocity `HandshakeIntent.java:16`, `StateRegistry.java:883`, `ProtocolVersion.java:89`; ViaVersion `InitialBaseProtocol.java:55,133`. Distinction: the *Handshake intent=3* is a serverbound signal from the transferring client; the *Transfer clientbound packet* (Play state, 0x73 in 1.20.5/1.21) is what the originating server sends to instruct the client to reconnect. Both ship in 1.20.5. Velocity registers the clientbound `TransferPacket` from `MINECRAFT_1_20_5` in both CONFIG (0x0B) and PLAY (0x73) state (`StateRegistry.java:239-240, 819-821`). **offline-mode encryption — CONFIRMED** version-stable. node-minecraft-protocol `server/login.js:52,88` gates the entire Encryption Request/Response on `needToVerify` with no protocol-version branch; minecraft.wiki's "as of 1.21" phrasing is documentation style for current vanilla behaviour, not a record of a 1.21 change. Stable since 1.8. **CONFIG packet IDs shift between 1.20.2 / 1.20.3 / 1.20.5 — CONFIRMED** true. Velocity `StateRegistry.java:163-261` documents the shifts (e.g. `Disconnect` stays 0x01 in 1.20.2, shifts to 0x02 in 1.20.5; `FinishConfiguration` shifts from 0x02→0x03). These are Velocity's values; vanilla wiki IDs may differ per-snapshot but the shift pattern is confirmed.