How the Minecraft server protocol works
What actually travels between a Minecraft client and server: the handshake, login and play states, packet structure, compression and encryption, and why the protocol is simple enough to reimplement.
The short version
The Minecraft Java protocol is a stream of length-prefixed packets over one TCP connection. The client opens the connection, sends a handshake naming the protocol version and the state it wants, then moves through login into the play state, after which both sides exchange packets describing chunks, entities, movement and chat. It is documented, small enough to implement by hand, and the reason unusual programs can act as a Minecraft server at all.
Most of what people believe about Minecraft servers is really about the game logic: ticks, entities, chunk loading, redstone. Underneath that there is a much smaller thing, a network protocol, and it is worth understanding separately, because it explains both how servers are built and why they can be built in strange places.
#One TCP connection, packets both ways
A Java Edition client connects to one TCP port, 25565 by default, and keeps that connection open for the whole session. There is no HTTP, no REST, no polling. Both ends push packets when they have something to say.
Every packet has the same outer shape:
[ VarInt: length ][ VarInt: packet ID ][ fields... ]
A VarInt is a variable-length integer: each byte carries seven bits of the value plus a continuation bit saying whether another byte follows. Small numbers take one byte, large ones take up to five. Since packet IDs are small and most lengths are modest, this saves a good deal of traffic over fixed-width integers.
The fields after the ID depend on two things: which packet ID it is, and which state the connection is currently in. The same numeric ID means different things in different states, which is why the state machine matters.
#The four states
Handshaking. The first packet the client ever sends. It carries the protocol version number, the address and port it thinks it is connecting to, and a "next state" field: 1 for status, 2 for login. That one field decides whether this connection is a server-list ping or a real join.
Status. This is the multiplayer list entry. The server replies with a JSON blob containing the MOTD, the version name, the protocol version, the player count and maximum, and optionally a base64 PNG favicon. Then a ping and pong exchange measures latency, and the connection closes. Everything you see in the server list, including the green or red connection bars, comes from this exchange and nothing else.
This is why a server that is badly behind on ticks can still show as online: the status response is cheap and the server can often still answer it while the play state is struggling.
Login. The client sends its username. On an online-mode server, the server responds with an encryption request, the client verifies the session against Mojang's authentication servers, and from that point the connection is encrypted with AES. The server may also enable packet compression above a size threshold. Turning online-mode off skips the authentication step, which is how offline servers work and why they are trivially impersonated.
Play. Everything else. Chunk data, block updates, entity spawns and movement, inventory, chat, sounds, particles, keep-alives. This is the state the connection spends its entire life in once you are in the world.
#What the server is actually sending
In the play state, the bulk of the traffic is world and entity data:
- Chunk packets carrying block data, biomes, lighting and block entities for one chunk column. This is why your view distance setting affects bandwidth and memory as much as it affects CPU.
- Entity packets spawning, moving, rotating and removing entities. The server only sends you entities within tracking range, which is why entity-heavy areas cost more the moment you walk into them.
- Keep-alives, which the server sends periodically and the client must answer. Miss enough of them and you are disconnected with a timeout, which is often what a badly stalled server looks like from the client side.
When something goes wrong at this layer, the errors are recognisable. A packet too large or malformed for the client to parse produces io.netty.handler.codec.DecoderException. A protocol version the server does not recognise produces Outdated server or outdated client, decided by a single integer in the handshake.
#Protocol version numbers
Every Minecraft release has a protocol version number, separate from the version string players see. The handshake carries the number, and the server compares it against its own. Mismatch, and you are rejected before login.
Proxies like ViaVersion work by translating packets between protocol versions, which is possible precisely because the format is well specified and mostly additive between releases.
#Why any of this matters
Two practical reasons.
It tells you where a problem lives. If the server list shows the server as up but joining hangs, the status state works and the play state does not, which points at the tick loop rather than at networking. If nothing appears in the server list at all, the connection never got as far as the handshake, so it is the port, the address or the firewall.
It explains what a server minimally is. A Minecraft server is a socket, a packet codec and a game loop. The socket and the codec are documented and small; the game loop is the enormous part. That gap is why software with no business doing it can be made to serve a real client: we have done it in Excel and Outlook with VBA over a Windows socket, in OBS Studio with its Python runtime, and in Obsidian as a JavaScript plugin. All four target 1.8.9 for the chunk-format reason above. Will It Host? has each build and its source.
The game loop is also where all the performance lives, which is a different article: what a Minecraft server actually needs to run.
#Frequently asked questions
How does a Minecraft server communicate with the client?
Over a single TCP connection, using length-prefixed binary packets. Each packet starts with a VarInt giving its length, then a VarInt packet ID, then fields whose meaning depends on that ID and on which protocol state the connection is in. There is no HTTP and no polling; both sides push packets as things happen.
What are the states in the Minecraft protocol?
Four. Handshaking, where the client states its protocol version and what it wants. Status, which serves the server list entry with the MOTD, version and player count. Login, which handles authentication, encryption and compression setup. Play, which is everything after that: chunks, entities, movement, chat and block changes.
What is a VarInt in the Minecraft protocol?
A variable-length integer that uses one to five bytes depending on how large the number is. Each byte carries seven bits of value and one continuation bit indicating whether another byte follows. Packet lengths and packet IDs are both VarInts, so small numbers cost one byte instead of four.
Can you write your own Minecraft server?
Yes, and people regularly do. The wire format is documented and the handshake and status exchange are simple enough to implement in an afternoon. A full play state, with world data, entity tracking and physics, is a much larger job, which is why most alternative implementations target a subset rather than parity with vanilla.
Written and maintained by the Hostd engineering team. Last updated 2026-08-10. Notice a mistake? Tell us.