Paper has become the dominant Bukkit-API server implementation for a reason — better performance, an actively maintained fork, and an API that keeps evolving. Here's what matters if you're building on it seriously.
Event-Driven Architecture
Everything in a Paper plugin should be a reaction to an event, not a polling loop. Register listeners with explicit priorities (`LOWEST` → `MONITOR`) and understand that `MONITOR`-priority listeners should never mutate the event — they're for observing final outcomes, not changing them. Get priority ordering wrong and two plugins that should cooperate will silently fight each other.
Async vs. Sync — The Rule That Actually Matters
Anything touching the Bukkit API (blocks, entities, inventories, world state) must run on the main thread. Anything slow — database calls, HTTP requests, file I/O — must not. The `BukkitScheduler` gives you both `runTaskAsynchronously` and `runTask`; the standard pattern is: do the slow work async, then hop back to the main thread with a follow-up sync task to actually touch the world. Blocking the main thread, even briefly, shows up directly as server-wide lag.
Folia Changes This
Folia's regionized multithreading splits the world into independently-ticked regions, each with its own thread. Plugins built assuming a single main thread need Folia-aware scheduling (region schedulers, entity schedulers) to run correctly — a growing consideration as more networks adopt it for scale.
Persistent Data Containers (PDC) Over Raw NBT
The PDC is the supported way to attach custom data to items, entities, and blocks without fighting Mojang's internal NBT format directly. One common gotcha: data isn't automatically copied between holders — cloning an ItemStack doesn't always carry PDC tags the way developers expect, and it's a frequent source of "why did my custom item lose its data" bugs.
Modern Commands: Brigadier, Not String Parsing
Paper's Brigadier-based command API (built on Mojang's own command framework) replaces manual string-splitting with typed argument parsing and built-in tab completion. One side effect worth knowing: registering commands this way can affect `/reload` behavior — another good reason to avoid relying on `/reload` in production at all, and restart cleanly instead.
Configuration
Bukkit's `FileConfiguration` covers basic YAML config needs; for more complex, versioned, or validated config, `ConfigurationSerializable` or a library like Configurate gives you type safety and migration paths as your config schema evolves across plugin versions.
Testing Without a Live Server
MockBukkit lets you unit-test plugin logic against a simulated server environment — catching regressions before they ever touch a production world, and making it realistic to run CI against plugin code the way you would any other software project.
Common Pitfalls
- Blocking the main thread with synchronous I/O or database calls.
- Ignoring event priority and silently conflicting with other plugins.
- Assuming PDC data survives ItemStack cloning without checking.
- Relying on `/reload` instead of a proper restart.
- Not testing against Folia if your plugin might run on a Folia-based network.
Recent API Shifts Worth Tracking
Paper's move to Mojang mappings, the Brigadier command API, the registries/lifecycle API for plugin bootstrapping, and a new Dialog API for structured UI are all relatively recent — if your reference material predates them, expect some real API drift.