Skip to main content
Minecraft Community Edition implements a custom network protocol for client-server communication, with platform-specific P2P implementations for console multiplayer.

Network Stack Overview

┌─────────────────────────────────────────┐
│         Application Layer               │
│  (PacketListener implementations)       │
├─────────────────────────────────────────┤
│         Packet Layer                    │
│  (Packet serialization/deserialization) │
├─────────────────────────────────────────┤
│         Connection Layer                │
│  (Buffering, threading, flow control)   │
├─────────────────────────────────────────┤
│         Transport Layer                 │
│  (Socket abstraction)                   │
├─────────────────────────────────────────┤
│    Platform Network Implementation      │
│  (TCP, P2P, Platform SDKs)              │
└─────────────────────────────────────────┘

Connection Class

The Connection class (Connection.h:20) provides the core networking functionality:
class Connection
{
private:
    static const int SEND_BUFFER_SIZE = 1024 * 5;
    static const int MAX_TICKS_WITHOUT_INPUT = 20 * 60;  // 1 minute
    
    Socket *socket;
    DataInputStream *dis;
    DataOutputStream *bufferedDos;
    
    // Packet queues
    queue<shared_ptr<Packet>> incoming;
    queue<shared_ptr<Packet>> outgoing;
    queue<shared_ptr<Packet>> outgoing_slow;  // Low-priority packets
    
    // Threading
    C4JThread* readThread;
    C4JThread* writeThread;
    C4JThread::Event* m_hWakeReadThread;
    C4JThread::Event* m_hWakeWriteThread;
    
    PacketListener *packetListener;
    bool running;
    
public:
    Connection(Socket *socket, const wstring& id, PacketListener *listener);
    
    void send(shared_ptr<Packet> packet);      // Immediate send
    void queueSend(shared_ptr<Packet> packet); // Queue for later
    void tick();
    void close(DisconnectPacket::eDisconnectReason reason, ...);
};
The Connection class uses separate read and write threads to prevent blocking and ensure smooth gameplay even with network latency.

Read Thread

int Connection::runRead(void* lpParam) {
    Connection *conn = (Connection*)lpParam;
    
    while (conn->running) {
        // Wait for data or wake event
        WaitForSingleObject(conn->m_hWakeReadThread, 100);
        
        // Read and process incoming packets
        if (!conn->readTick()) {
            break;  // Connection closed
        }
    }
    
    return 0;
}

bool Connection::readTick() {
    try {
        // Read packet ID
        int packetId = dis->read();
        
        // Create packet instance
        shared_ptr<Packet> packet = Packet::newPacket(packetId);
        
        // Deserialize packet data
        packet->read(dis);
        
        // Queue for processing on main thread
        EnterCriticalSection(&incoming_cs);
        incoming.push(packet);
        LeaveCriticalSection(&incoming_cs);
        
        return true;
    } catch (...) {
        return false;
    }
}

Write Thread

int Connection::runWrite(void* lpParam) {
    Connection *conn = (Connection*)lpParam;
    
    while (conn->running) {
        // Wait for packets or wake event
        WaitForSingleObject(conn->m_hWakeWriteThread, 100);
        
        // Write queued packets
        if (!conn->writeTick()) {
            break;
        }
    }
    
    return 0;
}

bool Connection::writeTick() {
    EnterCriticalSection(&writeLock);
    
    // Send high-priority packets first
    while (!outgoing.empty()) {
        shared_ptr<Packet> packet = outgoing.front();
        outgoing.pop();
        
        // Write packet ID
        bufferedDos->write(packet->getId());
        
        // Serialize packet data
        packet->write(bufferedDos);
    }
    
    // Send low-priority packets (rate-limited)
    if (slowWriteDelay <= 0 && !outgoing_slow.empty()) {
        shared_ptr<Packet> packet = outgoing_slow.front();
        outgoing_slow.pop();
        
        bufferedDos->write(packet->getId());
        packet->write(bufferedDos);
        
        slowWriteDelay = MINECRAFT_SERVER_SLOW_QUEUE_DELAY;
    }
    
    bufferedDos->flush();
    LeaveCriticalSection(&writeLock);
    
    return true;
}

Packet System

Packets are defined in net.minecraft.network.packet.h with a base class:
class Packet
{
public:
    static shared_ptr<Packet> newPacket(int id);
    
    virtual int getId() = 0;
    virtual void read(DataInputStream *input) = 0;
    virtual void write(DataOutputStream *output) = 0;
    virtual void handle(PacketListener *listener) = 0;
    virtual int getEstimatedSize() = 0;
};

Common Packet Types

  • PreLoginPacket: Initial handshake (UGC settings, texture pack info)
  • LoginPacket: Player authentication and spawn data
  • DisconnectPacket: Connection termination with reason
  • KeepAlivePacket: Connection health check (every 15 seconds)
  • MovePlayerPacket: Player position and rotation
  • TeleportEntityPacket: Instant position change
  • MoveEntityPacket: Entity position delta
  • RotateHeadPacket: Entity head rotation
  • ChunkVisibilityPacket: Send/remove chunk data
  • ChunkVisibilityAreaPacket: Set render distance
  • TileUpdatePacket: Single block change
  • BlockRegionUpdatePacket: Multiple block changes
  • AddPlayerPacket: Spawn player entity
  • AddMobPacket: Spawn mob entity
  • AddEntityPacket: Spawn generic entity
  • RemoveEntitiesPacket: Despawn entities
  • SetEntityDataPacket: Entity metadata update
  • PlayerActionPacket: Mining, interact, use item
  • UseItemPacket: Right-click/place block
  • AnimatePacket: Swing arm, take damage, etc.
  • InteractPacket: Entity interaction

Packet Example: MovePlayerPacket

class MovePlayerPacket : public Packet
{
public:
    double x, y, z;           // Position
    double stance;            // Eye height
    float yRot, xRot;        // Rotation
    bool onGround;
    
    virtual int getId() { return 13; }
    
    virtual void read(DataInputStream *input) {
        x = input->readDouble();
        y = input->readDouble();
        stance = input->readDouble();
        z = input->readDouble();
        yRot = input->readFloat();
        xRot = input->readFloat();
        onGround = input->readBoolean();
    }
    
    virtual void write(DataOutputStream *output) {
        output->writeDouble(x);
        output->writeDouble(y);
        output->writeDouble(stance);
        output->writeDouble(z);
        output->writeFloat(yRot);
        output->writeFloat(xRot);
        output->writeBoolean(onGround);
    }
    
    virtual void handle(PacketListener *listener) {
        listener->handleMovePlayer(shared_from_this());
    }
};

Platform-Specific Networking

Socket Abstraction

class Socket
{
public:
    virtual bool connect(const wstring& host, int port) = 0;
    virtual int read(byte *buffer, int length) = 0;
    virtual int write(const byte *buffer, int length) = 0;
    virtual void close() = 0;
    virtual bool isConnected() = 0;
    
    class SocketInputStream;
    class SocketOutputStream;
};

P2P Connection Manager

Each platform has its own P2P implementation:
// Common/Network/P2PConnectionManager.h
class P2PConnectionManager
{
public:
    virtual bool initialize() = 0;
    virtual Socket* createP2PSocket(PlayerUID remotePlayer) = 0;
    virtual bool acceptIncoming() = 0;
    virtual void tick() = 0;
};

// Windows64/Network/P2PConnectionManagerWin.h
class P2PConnectionManagerWin : public P2PConnectionManager
{
    // Uses Windows networking APIs
};

// Xbox implementations use Xbox Live services
// PlayStation implementations use PSN services
P2P connections require NAT traversal and may fail on restrictive networks. Always implement fallback to relay servers for production use.

Multiplayer Synchronization

Entity Synchronization

The server is authoritative for all entity state:
Client                          Server
  │                               │
  ├─── MovePlayerPacket ─────────>│
  │    (input/desired position)   │
  │                               ├─ Validate movement
  │                               ├─ Update entity
  │<──── MovePlayerPacket ────────┤
  │     (authoritative position)  │
  │                               │
  ├─ Update local position        │
  │   (smooth interpolation)      │

Client-Side Prediction

For responsive gameplay, clients predict their own movement:
void MultiplayerLocalPlayer::move(float xa, float za) {
    // 1. Apply movement locally (prediction)
    Player::move(xa, za);
    
    // 2. Send to server for validation
    shared_ptr<MovePlayerPacket> packet = make_shared<MovePlayerPacket>();
    packet->x = x;
    packet->y = y;
    packet->z = z;
    packet->yRot = yRot;
    packet->xRot = xRot;
    packet->onGround = onGround;
    
    connection->send(packet);
}
When the server sends back the authoritative position, the client smoothly interpolates if there’s a mismatch, avoiding jarring teleportation.

Chunk Loading

Chunks are streamed based on player position:
// Server sends view distance on login
ChunkVisibilityAreaPacket:
    int viewDistance = 8;  // chunks

// Server continuously sends chunks as player moves
for (int x = playerChunkX - viewDistance; x <= playerChunkX + viewDistance; x++) {
    for (int z = playerChunkZ - viewDistance; z <= playerChunkZ + viewDistance; z++) {
        if (!player->hasChunk(x, z)) {
            ChunkVisibilityPacket packet;
            packet.x = x;
            packet.z = z;
            packet.chunkData = level->getChunk(x, z)->serialize();
            connection->send(packet);
        }
    }
}

Block Updates

Block changes are broadcast to nearby players:
void ServerLevel::setTile(int x, int y, int z, int tile) {
    // Update server-side
    Level::setTile(x, y, z, tile);
    
    // Notify players in range
    TileUpdatePacket packet;
    packet.x = x;
    packet.y = y;
    packet.z = z;
    packet.tile = tile;
    packet.data = getData(x, y, z);
    
    for (ServerPlayer *player : getPlayersInRange(x, z, VIEW_DISTANCE)) {
        player->connection->send(packet);
    }
}

Network Optimization

Slow Queue System

Low-priority packets use a rate-limited queue to prevent bandwidth saturation:
// MinecraftServer.h:223
static int s_slowQueuePlayerIndex;
static int s_slowQueueLastTime;

static bool canSendOnSlowQueue(INetworkPlayer *player) {
    int now = currentTimeMillis();
    if (now - s_slowQueueLastTime < MINECRAFT_SERVER_SLOW_QUEUE_DELAY) {
        return false;  // Too soon
    }
    
    // Round-robin between players
    if (player->getIndex() != s_slowQueuePlayerIndex) {
        return false;
    }
    
    return true;
}
Slow queue is used for:
  • Non-critical entity updates
  • Distant chunk data
  • Particle effects
  • Sound events

Packet Batching

Multiple small updates can be batched:
class BlockRegionUpdatePacket : public Packet
{
    int x0, y0, z0, x1, y1, z1;  // Region bounds
    vector<BlockUpdate> updates;  // Multiple changes
};

// Instead of sending 100 TileUpdatePackets,
// send 1 BlockRegionUpdatePacket

Compression

Large data (chunks, textures) uses compression:
#include "compression.h"  // zlib wrapper

void ChunkVisibilityPacket::write(DataOutputStream *output) {
    // Serialize chunk data
    byte *rawData = serializeChunk();
    int rawSize = getChunkDataSize();
    
    // Compress
    byte *compressed = new byte[rawSize];
    int compressedSize = compress(rawData, rawSize, compressed);
    
    // Write compressed data
    output->writeInt(compressedSize);
    output->write(compressed, compressedSize);
}

Texture Synchronization

Custom skins and texture packs are synchronized:
class TexturePacket : public Packet
{
    wstring textureName;
    int width, height;
    byteArray imageData;  // PNG or compressed format
};

// Server workflow:
// 1. Player requests texture
// 2. Server checks if it has the texture
// 3. Server sends TexturePacket or requests from uploader
// 4. All players receive the texture for rendering
Texture packets use the slow queue to avoid overwhelming the network during multiplayer sessions.

Error Handling

Disconnect Reasons

namespace DisconnectPacket {
    enum eDisconnectReason {
        GENERIC,
        KICKED,
        OVERFLOW,        // Too many packets
        TIMEOUT,         // No data received
        PROTOCOL_ERROR,  // Invalid packet
        LOST_CONNECTION,
        SIGNED_OUT,      // User signed out of account
        PRIVILEGE_CHANGE // Lost multiplayer privilege
    };
}

Connection Timeout

void Connection::tick() {
    // Process incoming packets
    while (!incoming.empty()) {
        shared_ptr<Packet> packet = incoming.front();
        incoming.pop();
        
        packet->handle(packetListener);
        noInputTicks = 0;  // Reset timeout
    }
    
    // Check for timeout
    noInputTicks++;
    if (noInputTicks > MAX_TICKS_WITHOUT_INPUT) {
        close(DisconnectPacket::TIMEOUT);
    }
}

Best Practices

Minimize Packet Size

Use compact data types (shorts instead of ints where possible) and avoid sending redundant data.

Batch Updates

Group multiple changes into region update packets instead of individual updates.

Use Slow Queue

Put non-critical updates in the slow queue to prioritize gameplay packets.

Validate Server-Side

Never trust client data. Always validate positions, inventory changes, etc.

Client-Server

Learn about the client-server architecture

Overview

High-level architecture overview

Build docs developers (and LLMs) love