Skip to content

Network Module

The Network module provides a NeoForge networking abstraction with automatic packet registration. By using annotations and interface conventions, it eliminates the need to manually register each packet individually.

I. Core Interfaces

All network packets must implement one of the sub-interfaces of IPacket to declare their transmission direction:

InterfaceDescription
IClientboundPacketServer → Client (Clientbound)
IServerboundPacketClient → Server (Serverbound)
IInsensitiveBiPacketBidirectional, both sides share the same handler logic
ISensitiveBiPacketBidirectional, each side has its own independent handler logic

IClientboundPacket

java
public interface IClientboundPacket extends IPacket {
    /**
     * Logic executed on the client
     * @param player Always a LocalPlayer on the client
     */
    void handleOnClient(Player player);
}

IServerboundPacket

java
public interface IServerboundPacket extends IPacket {
    /**
     * Logic executed on the server
     * @param player Always a ServerPlayer on the server
     */
    void handleOnServer(Player player);
}

IInsensitiveBiPacket (Bidirectional, shared logic)

java
public interface IInsensitiveBiPacket extends IClientboundPacket, IServerboundPacket {
    /**
     * Same logic executed on both client and server
     */
    void handleOnBothSide(Player player);
}

ISensitiveBiPacket (Bidirectional, direction-sensitive)

java
public interface ISensitiveBiPacket extends IClientboundPacket, IServerboundPacket {
    // Implement handleOnClient and handleOnServer separately
}

II. @Network Annotation

The @Network annotation is applied to a package-info.java file to mark an entire package as a network packet package (the unit of scanning) and specify the protocol channel for all packets in that package.

java
// src/.../network/play/package-info.java
@Network(protocol = PacketProtocol.PLAY)
package com.example.mymod.network.play;

import dev.anvilcraft.lib.v2.network.register.Network;
import dev.anvilcraft.lib.v2.network.register.PacketProtocol;
PacketProtocol ValueNeoForge Channel
PLAY (default)In-game communication
CONFIGURATIONConnection configuration phase
COMMONBoth phases

III. Registering with NetworkRegistrar

Call in your RegisterPayloadHandlersEvent listener:

java
NetworkRegistrar.register(PayloadRegistrar registrar, String modId)

This method will:

  1. Scan all packages in the mod JAR annotated with @Network;
  2. Find classes in those packages that implement an IPacket sub-interface;
  3. Automatically register each packet according to its direction (Clientbound / Serverbound / Bidirectional).

IV. Full Example

1. Create a Packet

Here is a clientbound data sync packet:

java
package com.example.mymod.network.play;

import dev.anvilcraft.lib.v2.network.packet.IClientboundPacket;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Player;

public record SyncDataPacket(int value) implements IClientboundPacket {

    public static final Type<SyncDataPacket> TYPE =
        new Type<>(ResourceLocation.fromNamespaceAndPath("my_mod", "sync_data"));

    public static final StreamCodec<FriendlyByteBuf, SyncDataPacket> STREAM_CODEC =
        StreamCodec.composite(
            ByteBufCodecs.INT, SyncDataPacket::value,
            SyncDataPacket::new
        );

    @Override
    public Type<? extends CustomPacketPayload> type() {
        return TYPE;
    }

    @Override
    public void handleOnClient(Player player) {
        // Safely handle data on the client main thread
        ClientData.value = this.value;
    }
}

2. Annotate the Package

Create package-info.java in the same package:

java
@Network(protocol = PacketProtocol.PLAY)
package com.example.mymod.network.play;

import dev.anvilcraft.lib.v2.network.register.Network;
import dev.anvilcraft.lib.v2.network.register.PacketProtocol;

3. Register All Packets

java
import dev.anvilcraft.lib.v2.network.register.NetworkRegistrar;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;

@EventBusSubscriber(modid = "my_mod", bus = EventBusSubscriber.Bus.MOD)
public class MyModNetworking {

    @SubscribeEvent
    public static void onRegisterPayload(RegisterPayloadHandlersEvent event) {
        // "1" is the protocol version string
        var registrar = event.registrar("1");
        NetworkRegistrar.register(registrar, "my_mod");
    }
}

4. Sending Packets

java
// Server → specific client
PacketDistributor.sendToPlayer(serverPlayer, new SyncDataPacket(42));

// Server → all clients
PacketDistributor.sendToAllPlayers(new SyncDataPacket(42));

// Client → server
PacketDistributor.sendToServer(new MyServerboundPacket());

V. Bidirectional Packet Examples

java
// Bidirectional, direction-insensitive (same logic on both sides)
public record PingPacket(long timestamp) implements IInsensitiveBiPacket {

    public static final Type<PingPacket> TYPE = ...;
    public static final StreamCodec<FriendlyByteBuf, PingPacket> STREAM_CODEC = ...;

    @Override
    public Type<? extends CustomPacketPayload> type() { return TYPE; }

    @Override
    public void handleOnBothSide(Player player) {
        System.out.println("Ping received at: " + this.timestamp);
    }
}
java
// Bidirectional, direction-sensitive (different logic per side)
public record UpdatePacket(String data) implements ISensitiveBiPacket {

    @Override
    public void handleOnClient(Player player) {
        // Client-side handling
    }

    @Override
    public void handleOnServer(Player player) {
        // Server-side handling
    }
}

VI. Notes

  • Each packet class must declare static TYPE (CustomPacketPayload.Type) and STREAM_CODEC fields — NetworkRegistrar reads them via reflection;
  • handleOnClient, handleOnServer and handleOnBothSide callbacks are automatically dispatched to the main thread via ctx.enqueueWork() — no manual queuing needed;
  • It is recommended to place packets for different PacketProtocol values in separate sub-packages (e.g., network.play, network.configuration) each with their own @Network annotation;
  • NetworkRegistrar.register only scans classes within the same JAR — cross-mod packages are not scanned.

Released under the CC-BY-NC-SA 4.0 License.