Networking in Minecraft is used so the client and server can communicate with each other. Networking is a broad topic, so this page is split up into a few categories.
Why Is Networking Important?
Packets are the core concept of networking in Minecraft. Packets are made up of arbitrary data that can be sent either from server to client or from client to server. Check out the diagram below, which provides a visual representation of the networking architecture in Fabric:

Notice how packets are the bridge between the server and the client; that's because almost everything you do in the game involves networking in some way, whether you know it or not. For example, when you send a chat message, a packet is sent to the server with the content. The server then sends another packet to all the other clients with your message.
One important thing to keep in mind is there is always a server running, even in singleplayer and LAN. Packets are still used to communicate between the client and server even when no one else is playing with you. When talking about sides in networking, the terms "logical client" and "logical server" are used. The integrated singleplayer/LAN server and the dedicated server are both logical servers, but only the dedicated server can be considered a physical server.
When state is not synced between the client and server, you can run into issues where the server or other clients don't agree with what another client is doing. This is often known as a "desync". When writing your own mod you may need to send a packet of data to keep the state of the server and all clients in sync.
An Introduction to Networking
Defining a Payload
INFO
A payload is the data that is sent within a packet.
This can be done by creating a Java Record with a BlockPos parameter that implements CustomPayload.
java
public record SummonLightningS2CPayload(BlockPos pos) implements CustomPayload {
public static final Identifier SUMMON_LIGHTNING_PAYLOAD_ID = Identifier.of(FabricDocsReference.MOD_ID, "summon_lightning");
public static final CustomPayload.Id<SummonLightningS2CPayload> ID = new CustomPayload.Id<>(SUMMON_LIGHTNING_PAYLOAD_ID);
public static final PacketCodec<RegistryByteBuf, SummonLightningS2CPayload> CODEC = PacketCodec.tuple(BlockPos.PACKET_CODEC, SummonLightningS2CPayload::pos, SummonLightningS2CPayload::new);
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
At the same time, we've defined:
- An
Identifierused to identify our packet's payload. For this example our identifier will befabric-docs-reference:summon_lightning.
java
public static final Identifier SUMMON_LIGHTNING_PAYLOAD_ID = Identifier.of(FabricDocsReference.MOD_ID, "summon_lightning");1
- A public static instance of
CustomPayload.Idto uniquely identify this custom payload. We will be referencing this ID in both our common and client code.
java
public static final CustomPayload.Id<SummonLightningS2CPayload> ID = new CustomPayload.Id<>(SUMMON_LIGHTNING_PAYLOAD_ID);1
- A public static instance of a
PacketCodecso that the game knows how to serialize/deserialize the contents of the packet.
java
public static final PacketCodec<RegistryByteBuf, SummonLightningS2CPayload> CODEC = PacketCodec.tuple(BlockPos.PACKET_CODEC, SummonLightningS2CPayload::pos, SummonLightningS2CPayload::new);1
We have also overridden getId to return our payload ID.
java
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}1
2
3
4
2
3
4
Registering a Payload
Before we send a packet with our custom payload, we need to register it.
INFO
S2C and C2S are two common suffixes that mean Server-to-Client and Client-to-Server respectively.
This can be done in our common initializer by using PayloadTypeRegistry.playS2C().register which takes in a CustomPayload.Id and a PacketCodec.
java
PayloadTypeRegistry.playS2C().register(SummonLightningS2CPayload.ID, SummonLightningS2CPayload.CODEC);1
A similar method exists to register client-to-server payloads: PayloadTypeRegistry.playC2S().register.
Sending a Packet to the Client
To send a packet with our custom payload, we can use ServerPlayNetworking.send which takes in a ServerPlayerEntity and a CustomPayload.
Let's start by creating our Lightning Tater item. You can override use to trigger an action when the item is used. In this case, let's send packets to the players in the server world.
java
public class LightningTaterItem extends Item {
public LightningTaterItem(Settings settings) {
super(settings);
}
@Override
public ActionResult use(World world, PlayerEntity user, Hand hand) {
if (world.isClient()) {
return ActionResult.PASS;
}
SummonLightningS2CPayload payload = new SummonLightningS2CPayload(user.getBlockPos());
for (ServerPlayerEntity player : PlayerLookup.world((ServerWorld) world)) {
ServerPlayNetworking.send(player, payload);
}
return ActionResult.SUCCESS;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Let's examine the code above.
We only send packets when the action is initiated on the server, by returning early with a isClient check:
java
if (world.isClient()) {
return ActionResult.PASS;
}1
2
3
2
3
We create an instance of the payload with the user's position:
java
SummonLightningS2CPayload payload = new SummonLightningS2CPayload(user.getBlockPos());1
Finally, we get the players in the server world through PlayerLookup and send a packet to each player.
java
for (ServerPlayerEntity player : PlayerLookup.world((ServerWorld) world)) {
ServerPlayNetworking.send(player, payload);
}1
2
3
2
3
INFO
Fabric API provides PlayerLookup, a collection of helper functions that will look up players in a server.
A term frequently used to describe the functionality of these methods is "tracking". It means that an entity or a chunk on the server is known to a player's client (within their view distance) and the entity or block entity should notify tracking clients of changes.
Tracking is an important concept for efficient networking, so that only the necessary players are notified of changes by sending packets.
Receiving a Packet on the Client
To receive a packet sent from a server on the client, you need to specify how you will handle the incoming packet.
This can be done in the client initializer, by calling ClientPlayNetworking.registerGlobalReceiver and passing a CustomPayload.Id and a PlayPayloadHandler, which is a Functional Interface.
In this case, we'll define the action to trigger within the implementation of PlayPayloadHandler implementation (as a lambda expression).
java
ClientPlayNetworking.registerGlobalReceiver(SummonLightningS2CPayload.ID, (payload, context) -> {
ClientWorld world = context.client().world;
if (world == null) {
return;
}
BlockPos lightningPos = payload.pos();
LightningEntity entity = EntityType.LIGHTNING_BOLT.create(world, SpawnReason.TRIGGERED);
if (entity != null) {
entity.setPosition(lightningPos.getX(), lightningPos.getY(), lightningPos.getZ());
world.addEntity(entity);
}
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Let's examine the code above.
We can access the data from our payload by calling the Record's getter methods. In this case payload.pos(). Which then can be used to get the x, y and z positions.
java
BlockPos lightningPos = payload.pos();1
Finally, we create a LightningEntity and add it to the world.
java
LightningEntity entity = EntityType.LIGHTNING_BOLT.create(world, SpawnReason.TRIGGERED);
if (entity != null) {
entity.setPosition(lightningPos.getX(), lightningPos.getY(), lightningPos.getZ());
world.addEntity(entity);
}1
2
3
4
5
6
2
3
4
5
6
Now, if you add this mod to a server and when a player uses our Lightning Tater item, every player will see lightning striking at the user's position.
Sending a Packet to the Server
Just like sending a packet to the client, we start by creating a custom payload. This time, when a player uses a Poisonous Potato on a living entity, we request the server to apply the Glowing effect to it.
java
public record GiveGlowingEffectC2SPayload(int entityId) implements CustomPayload {
public static final Identifier GIVE_GLOWING_EFFECT_PAYLOAD_ID = Identifier.of(FabricDocsReference.MOD_ID, "give_glowing_effect");
public static final CustomPayload.Id<GiveGlowingEffectC2SPayload> ID = new CustomPayload.Id<>(GIVE_GLOWING_EFFECT_PAYLOAD_ID);
public static final PacketCodec<RegistryByteBuf, GiveGlowingEffectC2SPayload> CODEC = PacketCodec.tuple(PacketCodecs.INTEGER, GiveGlowingEffectC2SPayload::entityId, GiveGlowingEffectC2SPayload::new);
@Override
public Id<? extends CustomPayload> getId() {
return ID;
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
We pass in the appropriate codec along with a method reference to get the value from the Record to build this codec.
Then we register our payload in our common initializer. However, this time as Client-to-Server payload by using PayloadTypeRegistry.playC2S().register.
java
PayloadTypeRegistry.playC2S().register(GiveGlowingEffectC2SPayload.ID, GiveGlowingEffectC2SPayload.CODEC);1
To send a packet, let's add an action when the player uses a Poisonous Potato. We'll be using the UseEntityCallback event to keep things concise.
We register the event in our client initializer, and we use isClient() to ensure that the action is only triggered on the logical client.
java
UseEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> {
if (!world.isClient()) {
return ActionResult.PASS;
}
ItemStack usedItemStack = player.getStackInHand(hand);
if (entity instanceof LivingEntity && usedItemStack.isOf(Items.POISONOUS_POTATO) && hand == Hand.MAIN_HAND) {
GiveGlowingEffectC2SPayload payload = new GiveGlowingEffectC2SPayload(hitResult.getEntity().getId());
ClientPlayNetworking.send(payload);
return ActionResult.SUCCESS;
}
return ActionResult.PASS;
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
We create an instance of our GiveGlowingEffectC2SPayload with the necessary arguments. In this case, the network ID of the targeted entity.
java
GiveGlowingEffectC2SPayload payload = new GiveGlowingEffectC2SPayload(hitResult.getEntity().getId());1
Finally, we send a packet to the server by calling ClientPlayNetworking.send with the instance of our GiveGlowingEffectC2SPayload.
java
ClientPlayNetworking.send(payload);1
Receiving a Packet on the Server
This can be done in the common initializer, by calling ServerPlayNetworking.registerGlobalReceiver and passing a CustomPayload.Id and a PlayPayloadHandler.
java
ServerPlayNetworking.registerGlobalReceiver(GiveGlowingEffectC2SPayload.ID, (payload, context) -> {
Entity entity = context.player().getWorld().getEntityById(payload.entityId());
if (entity instanceof LivingEntity livingEntity && livingEntity.isInRange(context.player(), 5)) {
livingEntity.addStatusEffect(new StatusEffectInstance(StatusEffects.GLOWING, 100));
}
});1
2
3
4
5
6
7
2
3
4
5
6
7
INFO
It is important that you validate the content of the packet on the server side.
In this case, we validate if the entity exists based on its network ID.
java
Entity entity = context.player().getWorld().getEntityById(payload.entityId());1
Additionally, the targeted entity has to be a living entity, and we restrict the range of the target entity from the player to 5.
java
if (entity instanceof LivingEntity livingEntity && livingEntity.isInRange(context.player(), 5)) {1
Now when any player tries to use a Poisonous Potato on a living entity, the glowing effect will be applied to it.
















