Skip to main content
Foundation provides utilities for working with Minecraft packets through ProtocolLib integration, allowing you to send custom packets and intercept network traffic.

Setup

Foundation’s packet utilities require ProtocolLib to be installed on your server.

Add dependency

Add ProtocolLib to your plugin.yml:
depend: [ProtocolLib]
Or as a soft dependency:
softdepend: [ProtocolLib]

Packet listeners

Create packet listeners by extending PacketAdapter and using the @AutoRegister annotation:
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import org.mineacademy.fo.annotation.AutoRegister;

@AutoRegister
public final class ChatPacketListener extends PacketAdapter {

    public ChatPacketListener() {
        super(SimplePlugin.getInstance(), 
              PacketType.Play.Client.CHAT);
    }

    @Override
    public void onPacketReceiving(PacketEvent event) {
        String message = event.getPacket().getStrings().read(0);
        Player player = event.getPlayer();
        
        if (message.startsWith("/secret")) {
            event.setCancelled(true);
            player.sendMessage("Secret command executed!");
        }
    }
}

Common packet types

Sending packets

import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.wrappers.WrappedChatComponent;

public void sendChatPacket(Player player, String message) {
    PacketContainer packet = new PacketContainer(PacketType.Play.Server.CHAT);
    
    packet.getChatComponents().write(0, 
        WrappedChatComponent.fromText(message));
    packet.getBytes().write(0, (byte) 1); // System message
    
    ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet);
}

Intercepting packets

@AutoRegister
public final class BlockPlaceListener extends PacketAdapter {

    public BlockPlaceListener() {
        super(SimplePlugin.getInstance(),
              PacketType.Play.Client.BLOCK_PLACE);
    }

    @Override
    public void onPacketReceiving(PacketEvent event) {
        Player player = event.getPlayer();
        BlockPosition pos = event.getPacket()
            .getBlockPositionModifier().read(0);
        
        Location loc = pos.toLocation(player.getWorld());
        
        // Check if player can build here
        if (!canBuildAt(player, loc)) {
            event.setCancelled(true);
        }
    }
}

Modifying packets

Change outgoing chat

@AutoRegister
public final class ChatModifierListener extends PacketAdapter {

    public ChatModifierListener() {
        super(SimplePlugin.getInstance(),
              PacketType.Play.Server.CHAT);
    }

    @Override
    public void onPacketSending(PacketEvent event) {
        StructureModifier<WrappedChatComponent> components = 
            event.getPacket().getChatComponents();
        
        WrappedChatComponent original = components.read(0);
        String json = original.getJson();
        
        // Modify the JSON
        String modified = json.replace("bad_word", "***");
        
        components.write(0, 
            WrappedChatComponent.fromJson(modified));
    }
}

Fake block changes

public void sendFakeBlock(Player player, Location loc, Material type) {
    PacketContainer packet = new PacketContainer(
        PacketType.Play.Server.BLOCK_CHANGE);
    
    packet.getBlockPositionModifier().write(0, 
        new BlockPosition(loc.toVector()));
    packet.getBlockData().write(0, 
        WrappedBlockData.createData(type));
    
    ProtocolLibrary.getProtocolManager()
        .sendServerPacket(player, packet);
}

Temporary players

Create temporary player instances for packet sending:
import com.comphenix.protocol.wrappers.WrappedGameProfile;

public void sendPacketAsNPC(Player viewer, String npcName) {
    WrappedGameProfile profile = new WrappedGameProfile(
        UUID.randomUUID(), npcName);
    
    // Create and send player info packet
    PacketContainer packet = new PacketContainer(
        PacketType.Play.Server.PLAYER_INFO);
    
    // Configure packet with profile
    // ...
    
    ProtocolLibrary.getProtocolManager()
        .sendServerPacket(viewer, packet);
}

Best practices

1

Use @AutoRegister

Always use @AutoRegister annotation for automatic listener registration:
@AutoRegister
public final class MyPacketListener extends PacketAdapter {
    // ...
}
2

Make classes final

Packet listeners should be final classes:
public final class MyListener extends PacketAdapter {
    // Good
}
3

Handle async carefully

Packet events can be async. Use schedulers when needed:
@Override
public void onPacketReceiving(PacketEvent event) {
    Common.runLater(() -> {
        // Safe main thread operation
    });
}
4

Check packet validity

Always validate packet data:
@Override
public void onPacketReceiving(PacketEvent event) {
    if (event.isCancelled())
        return;
    
    if (!event.isPlayerTemporary()) {
        // Process packet
    }
}

Common use cases

Anti-cheat integration

@AutoRegister
public final class MovementListener extends PacketAdapter {

    public MovementListener() {
        super(SimplePlugin.getInstance(),
              PacketType.Play.Client.POSITION,
              PacketType.Play.Client.POSITION_LOOK);
    }

    @Override
    public void onPacketReceiving(PacketEvent event) {
        Player player = event.getPlayer();
        
        double x = event.getPacket().getDoubles().read(0);
        double y = event.getPacket().getDoubles().read(1);
        double z = event.getPacket().getDoubles().read(2);
        
        // Check for illegal movement
        if (isIllegalMove(player, x, y, z)) {
            event.setCancelled(true);
            flagPlayer(player);
        }
    }
}

Custom tab list

public void updateTabList(Player player) {
    PacketContainer packet = new PacketContainer(
        PacketType.Play.Server.PLAYER_INFO);
    
    packet.getPlayerInfoAction().write(0, 
        EnumWrappers.PlayerInfoAction.UPDATE_DISPLAY_NAME);
    
    // Add player data
    List<PlayerInfoData> data = new ArrayList<>();
    // Configure data...
    
    packet.getPlayerInfoDataLists().write(0, data);
    
    ProtocolLibrary.getProtocolManager()
        .sendServerPacket(player, packet);
}
Packet listeners are registered automatically when using @AutoRegister. No manual registration needed!
Improper packet manipulation can crash clients or cause desyncs. Always test thoroughly!

Build docs developers (and LLMs) love