
ProtocolLib: Building Advanced Minecraft Server Plugins in 2026
dmulloy2/ProtocolLib
Provides read and write access to the Minecraft protocol with Bukkit.
View on GitHub ↗Ever tried to build a Minecraft server plugin that does something the standard Bukkit API won't let you do? Yeah, that's where most developers hit a wall. You either reverse-engineer obfuscated Minecraft code (which breaks every update), hook into internal CraftBukkit classes (and pray no other plugin does the same thing), or you just give up. ProtocolLib is the project that exists specifically so you don't have to.
What This Project Solves
ProtocolLib is a Java library that gives you clean, safe access to the Minecraft network protocol. Instead of wrestling with obfuscated bytecode or dodgy reflection hacks, you get a proper event API that lets you listen to, modify, or cancel packets sent between the server and clients. Think of it like Bukkit events, but for the protocol layer.
The big win: you don't reference CraftBukkit at all. No version-specific imports that break in 1.21. No fragile assumptions about internal class structures. Just packet listeners that stay stable across updates.
Real Minecraft Use Cases
Chat filtering and moderation is the most obvious application. Listen for client-side chat packets, read the message content through ProtocolLib's API, and cancel the packet if the player tries something you've blocked. The message never even reaches the server. Clean, simple, and it works every single update.
Custom network features are where things get interesting. You could build invisible particle effects, modify player appearance without skin changes, or send data to the server that the vanilla protocol doesn't officially support. A dueling system with custom visuals? A voting interface that responds to player movement packets? These are the kinds of things ProtocolLib makes possible.
But the real reason most people reach for it's plugin compatibility. When multiple plugins hook the same internal classes, everything gets messy. Plugins crash. Data corrupts silently. ProtocolLib centralizes packet handling so different plugins can actually coexist without stepping on each other's toes. That's not glamorous, but it's what keeps servers running smoothly.
Installation and Setup
If you're building a Bukkit plugin, the setup is straightforward. Add the dependency to your build file first.
For Maven:
<dependencies>
<dependency>
<groupId>net.dmulloy2</groupId>
<artifactId>ProtocolLib</artifactId>
<version>5.4.0</version>
<scope>provided</scope>
</dependency>
</dependencies>Note that version 5.4.0 requires Java 17 or higher, so make sure your build environment is up to date. If you're on an older server, grab an earlier release instead.
Gradle users should add it like this:
repositories {
mavenCentral()
}
dependencies {
compileOnly 'net.dmulloy2:ProtocolLib:5.4.0'
}Next, add ProtocolLib as a dependency in your plugin.yml:
depend: [ ProtocolLib ]This tells the server that your plugin won't start unless ProtocolLib loads first. Critical for avoiding null pointer exceptions on startup. Then grab a reference to the ProtocolManager in your plugin's onLoad() method:
private ProtocolManager protocolManager;
public void onLoad() {
protocolManager = ProtocolLibrary.getProtocolManager();
}That's it. You're ready to start listening for packets.
How Packet Listeners Work
The simplest listener just disables something. Say you want to mute all sound effects server-wide:
protocolManager.addPacketListener(new PacketAdapter(
this,
ListenerPriority.NORMAL,
PacketType.Play.Server.NAMED_SOUND_EFFECT
) {
@Override
public void onPacketSending(PacketEvent event) {
event.setCancelled(true);
}
});Most of the complexity is right there. You create a PacketAdapter, specify which packet type you're listening for, override the callback, and modify the event.
Where it gets useful is reading and changing packet content. Here's a global chat filter:
protocolManager.addPacketListener(new PacketAdapter(
this,
ListenerPriority.NORMAL,
PacketType.Play.Client.CHAT
) {
@Override
public void onPacketReceiving(PacketEvent event) {
PacketContainer packet = event.getPacket();
String message = packet.getStrings().read(0);
if (message.contains("badword")) {
event.setCancelled(true);
}
}
});The getPacket() call gives you the packet data. You read fields by type (strings, integers, bytes) using their index. No field names, just positions. It's abstract at first, but it means you're never dependent on Minecraft's internal naming, which changes constantly with obfuscation.
You can modify packets too. Packet data is mutable, so you can write new values before they're sent. Listener priority matters as well - if multiple plugins listen to the same packet type, LOWEST runs first, HIGHEST runs last, letting you control execution order.
Key Features That Make This Worth Using
The abstraction layer is the main thing. Instead of dealing with obfuscated NMS (net.minecraft.server) classes, ProtocolLib wraps everything into typed accessors. Want to read player position? It's just packet.getDoubles().read(0), not hunting through a maze of getter methods on some internal class that probably renamed itself last snapshot.
Version compatibility is huge. ProtocolLib maintains packet definitions for every major Minecraft release. When you update your server from 1.20 to 1.21.4, your ProtocolLib code just works. The library handles protocol changes internally. You don't recompile anything.
Packet injection is another solid feature. You can create packets from scratch and send them to players:
PacketContainer packet = protocolManager.createPacket(PacketType.Play.Server.NAMED_SOUND_EFFECT);
// populate fields...
protocolManager.sendServerPacket(player, packet);That's clean compared to trying to instantiate NMS packet classes directly. And again, it survives updates without modification.
The library also includes helper classes like WrappedChatComponent, ChunkData, and PlayerInfoData - basically anything tricky to construct gets wrapped with convenient builders. The recent 5.4.0 release fixed initialization issues for versions 1.20.4 through 1.21.5, so they're actively maintained.
What Trips New Users Up
The index-based field access takes getting used to. You look up the packet wiki to figure out which index corresponds to which field. For common packets it's documented, but if you're dealing with something custom or very recent, you're on your own. That's the tradeoff for not being tied to internal class names.
Thread safety matters. Minecraft runs packet handling on the network thread, and your listener code runs there too. If you do anything heavy, schedule it on the main server thread via Bukkit.getScheduler(). Blocking your packet listener stalls the entire network pipeline.
Version mismatches will destroy you. If you compile against ProtocolLib 5.4.0 but the server has 5.3.0 installed, you get runtime class not found errors. Match versions exactly. Don't rely on features from newer releases unless you specify a minimum version in your plugin.yml.
Debugging is harder than regular code because everything is listener-based. If a packet isn't behaving right, add logging at each step. Verify your listener is being called. Check that field indices match what you expect. The protocol wiki is your friend here.
Alternatives and When to Consider Them
PacketEvents exists as another option, though it's newer and less battle-tested. If you're building something that needs deep protocol access with modern tooling and don't mind being on slightly less stable ground, PacketEvents might appeal to you. But ProtocolLib is the established choice - 1285 GitHub stars, actively maintained, and the latest release supports Minecraft 1.21.4 through 1.21.8.
Some developers just work directly with NMS for highly specialized use cases. That's usually a mistake because you're signing up for maintenance work every Minecraft release. ProtocolLib handles that for you.
If you need to explore what players can do with your server, check out our Minecraft Skin Creator and Minecraft Block Search tools.
dmulloy2/ProtocolLib - GPL-2.0, ★1285

