// voxen field notes
How to Port a Spigot Plugin to Paper and Purpur (2026 Guide)
· 7 min read · [ GUIDE ] · by Matteo Orazietti
To port a Spigot plugin to Paper, recompile the source against the Paper API, replace any CraftBukkit internals with stable Bukkit API calls, and verify the result on a staging Paper or Purpur server. Most plugins that only use the Bukkit API need little more than a rebuild; plugins that touch NMS, reflection, or version-specific internals need real migration work. This guide shows how to tell which case you have, the exact porting sequence, where Purpur differs, and how to test without risking your live server.
Do Spigot plugins already run on Paper and Purpur?
Usually, yes — and understanding why tells you when porting is trivial and when it is not. Paper is a fork of Spigot (technically of CraftBukkit) that keeps full compatibility with the Bukkit and Spigot APIs, and Purpur is a fork of Paper that keeps compatibility with both. A plugin compiled against the Spigot API therefore loads on Paper and Purpur as long as it only calls stable API methods. The problems start when a plugin reaches past the API into the server implementation: net.minecraft.server classes,org.bukkit.craftbukkit classes, reflection against internals, or methods that changed between Minecraft versions. If you are unsure how your plugin gets onto the server in the first place, read the installation guide for Spigot, Paper and Purpur first — the porting steps below assume the .jar already loads somewhere.
| Platform | What it is | Porting effort from Spigot |
|---|---|---|
| Spigot | The baseline Bukkit implementation most plugins target | Zero — this is the starting point |
| Paper | Spigot fork with performance patches plus extra API methods | Rebuild; small fixes only if you touch internals |
| Purpur | Paper fork adding gameplay configuration and extra API | Same as Paper unless you use Purpur-only features |
Practical rule: if the plugin was written against the Bukkit or Spigot API and declares a modernapi-version, try it on Paper unchanged on a test server before porting anything. Many owners discover there is nothing to port. Only when the console shows NoClassDefFoundError,NoSuchMethodError, or warnings about unsupported API versions does the checklist below apply. Version jumps add a second dimension — a 1.8-era plugin moved to Paper 1.21 needs a version migration on top of the platform port, which is covered in our guide to updating a plugin to 1.21.
What actually breaks when you port a plugin?
Almost every porting failure falls into one of five buckets. Knowing which bucket your errors belong to decides whether the port takes twenty minutes or two days.
- CraftBukkit and NMS imports. Any import starting with
org.bukkit.craftbukkitornet.minecraft.serveris tied to one server implementation and often one Minecraft version. These are the most common breakage: the class moved, the package gained a version suffix, or Paper relocated it. Each one must be replaced with a stable API call or a Paper API equivalent. - Reflection into internals. Some plugins avoid direct NMS imports by reaching into private fields and methods via reflection. This compiles fine and then explodes at runtime with
NoSuchFieldExceptionorIllegalAccessExceptionon the new platform. Search forgetDeclaredField,getDeclaredMethodandsetAccessible— every hit is a suspect. - Removed or renamed API methods. The Bukkit API deprecates slowly and removes rarely, but across big version jumps (1.8 to 1.13 materials, 1.12 to 1.13 sound names, 1.20 to 1.21 component changes) old calls stop existing. The compiler catches these, which makes them the easiest bucket: fix each error it reports.
- Missing api-version declaration. Old
plugin.ymlfiles withoutapi-versionstill load, but modern servers treat them as legacy and apply compatibility layers that change behavior subtly. Settingapi-versionto your target version is a one-line change that removes a whole class of weirdness. - Paper-only or Spigot-only touches. Going the other direction also matters: if the plugin already uses
com.destroystokyo.paperorio.papermc.paperclasses, it never ran on plain Spigot to begin with, and porting means deciding which platform is now the baseline. The Paper documentation marks clearly which methods exist only on Paper.
How do you port a Spigot plugin to Paper step by step?
Work in this order. Each step is cheap; skipping ahead is how people lose afternoons to phantom errors caused by step one.
- Inventory the internals. Search the source for
org.bukkit.craftbukkit,net.minecraft.server,com.mojang, and reflection calls. Zero hits means you likely have a clean API plugin — skip to step four. A handful of hits means a normal afternoon port. Dozens of hits across many files means an NMS-heavy plugin where you should budget real time or consider assisted porting (more on that below). - Switch the dependency to the Paper API. In your Maven or Gradle build file, replace the Spigot API dependency with the Paper API for your exact target Minecraft version. The Paper API contains the full Bukkit and Spigot API surface plus Paper additions, so clean plugins compile without further changes. Keep the Minecraft version identical to your server — compiling against 1.20 API for a 1.21 server just moves the errors to runtime.
- Fix every compile error deliberately. For each NMS or CraftBukkit usage, look for the stable-API equivalent first: scoreboards, attributes, persistent data containers and the scheduler all absorbed use cases that once required internals. The SpigotMC API documentation and the Paper Javadocs together cover nearly every replacement; only genuinely version-specific behavior (custom entities, packet-level tricks) needs platform-specific code with a version check.
- Set api-version and audit plugin.yml. Add the
api-versionmatching your target (for example1.21), confirm themainclass path, commands and permissions are still accurate, and remove workarounds that only existed for ancient versions. - Rebuild from clean. Run a clean build so no stale classes from the Spigot compile linger in the output directory, then inspect the warnings: deprecation warnings on a fresh port are a to-do list for the next Minecraft update, not something to ignore.
- Stage-test on Paper before touching production. Install the rebuilt .jar on a staging Paper server running the exact version and Java release of production, exercise every command and event path, and read the full startup log — Paper logs legacy-material handling and API misuse explicitly. Only then copy the .jar to production.
grep -rn "org.bukkit.craftbukkit\|net.minecraft.server\|getDeclaredField" src/ | head -30
That single search decides the shape of the whole job. If it returns nothing, the port is a dependency swap and a rebuild. If it returns a page of hits, work through them one file at a time rather than rewriting — most NMS usages in typical plugins are three or four repeated patterns (item NBT, entity handles, title packets on old versions), and fixing the pattern once fixes every occurrence.
What changes when the target is Purpur?
Less than most owners expect. Because Purpur builds directly on Paper, a plugin compiled against the Paper API loads on Purpur unchanged — there is no separate Purpur porting step for ordinary plugins. Pin the Paper API version that matches your Purpur server build, run the same staged test on Purpur itself (not just Paper), and you are done. Extra work only appears in two cases: you want to call Purpur-only API (configuration options, gameplay events Purpur adds), in which case you add the Purpur API artifact and accept that the plugin now requires Purpur; or your staging shows behavior differences from Purpur configuration defaults (riding mechanics, entity settings), which is server administration, not plugin code. One caution: never compile against Purpur API "just in case" if you intend to support plain Paper too — the dependency direction only goes one way.
How do you test a ported plugin before production?
Ported plugins fail in boring, preventable ways when testing is skipped: a command nobody tried, an event that only fires with two players online, a config default that only matters on first run. Run this checklist on the staging server and most of them surface in minutes.
- Mirror production versions exactly — same Paper or Purpur build, same Java release, same
api-versionexpectations. A port tested on Java 21 for a Java 17 host proves nothing. - Read the whole startup log, not just errors in red. Warnings about legacy authors, relocated classes and deprecated events are the port talking to you.
- Exercise every trigger — each command, permission node, join/quit path, and scheduled task at least once, including the admin commands players never run.
- Soak-test with two or three players doing normal things for half an hour. Concurrency and timing bugs from scheduler differences between Spigot and Paper only appear under real play.
- Keep the old .jar and a world backup until the ported build has survived a full play session. Rollback beats debugging at midnight.
When is porting by hand not worth it?
Hand-porting assumes you have the source, a working build setup, and an afternoon. If you only have the compiled .jar — an abandoned plugin whose author vanished — the manual chain becomes decompile, migrate, fix decompiler damage, rebuild, which is a different and much longer job. The same applies to NMS-heavy plugins where every file touches internals. In those cases Voxen and its Plugin Porter handle the whole chain: you upload the .jar or a source archive, pick Paper or Purpur and the target Minecraft version, and it decompiles, migrates and recompiles the plugin in one flow. The honest limits are stated up front — obfuscated jars are declined rather than mangled, and NMS-heavy ports come back with warnings about what could only be partially migrated. Full walkthrough and supported versions are in the Voxen documentation.
