Guidefor developers3 min read
What breaks plugins when Minecraft gets a new drop
Why a plugin that worked yesterday fails today, which kinds of breakage are recoverable, and how to write one that survives the next drop.
Three kinds of breakage
They fail differently and cost very different amounts to fix, so it is worth knowing which one you are looking at.
The API changed
A method was removed, renamed, or its signature changed. Your plugin calls it, the method is
not there, and the plugin fails to load with a NoSuchMethodError naming exactly what it
wanted.
This is the cheapest kind. The error names the problem, the replacement is usually documented, and the fix is a recompile.
You were not using the API
A plugin that reaches into the server's internal implementation classes, what people call NMS, is reaching into code with no compatibility promise at all. Those classes are obfuscated, renamed between versions, and restructured whenever it suits.
This breaks on every single version, and the fix is a rewrite for each one. It is why some plugins update within hours of a release and others take a month: the ones that stayed inside the API mostly just recompile.
If you are writing a plugin, this is the decision that determines how much of your life it consumes. Use the API. When the API genuinely cannot do it, isolate the internal code behind your own interface so the version-specific part is one small file rather than scattered through the project.
The behaviour changed
The nastiest kind. Everything compiles, everything loads, and something behaves differently: an event fires at a different point, a value that was never null now can be, an ordering changed.
Nothing errors. Your plugin is just wrong, and you find out from a player report a week later. The only defence is testing on the pre-releases, where the behaviour is settled enough to be worth testing against and the release has not happened yet.
Reading the failure
[ERROR]: Could not load 'plugins/Example.jar'
java.lang.NoSuchMethodError: 'org.bukkit.inventory.ItemStack
org.bukkit.inventory.Inventory.getItem(int)'
The method it wanted is named in full. Find its replacement in the current API and recompile.
java.lang.NoClassDefFoundError: net/minecraft/server/v1_21_R1/EntityPlayer
A versioned internal class. This is the second kind, and it is a rewrite rather than a recompile.
[WARN]: Plugin Example v1.0 does not specify an api-version
Not a failure. Paper is applying legacy compatibility handling because your plugin.yml did
not say which API generation you target. Fix it by declaring one; see
plugin.yml api-version values.
Set api-version honestly
api-version tells the server which API generation your plugin was written against, so it
knows whether to apply compatibility handling for older behaviour.
Set it to the oldest version you actually support and have tested. Setting it to the newest release because that is what you happen to be running turns a handled compatibility case into a real bug: the server stops applying the handling your plugin still needs.
Testing before the release, not after
The release cycle exists to give you this chance:
- Snapshots. The API is still moving. Watching, not testing.
- Pre-releases. Feature complete. This is where testing pays: what you find now is fixable before anyone runs it.
- Release candidates. This is the build that ships unless something serious turns up. A green run here means a green run on release day.
Server projects publish builds for these. Paper's build history shows every one with its channel, so you can see when an experimental build for a new version first appeared.
Writing one that survives
- Stay in the API. Almost everything a plugin needs is there. Reaching past it is a decision to maintain it forever.
- Isolate what you cannot. One version-specific class behind your own interface, so a new Minecraft version is one file rather than a search through the project.
- Depend on API ranges, not exact versions. A patch release of a library should not break your plugin for everyone.
- Declare your supported versions accurately in your release metadata. That is what catalogues read, including the plugin catalogue here, which filters on exactly those declarations. Writing the release line when you mean one version, or the reverse, is what makes users and catalogues disagree about what you support.
- Publish a build for the release candidate. Server operators plan upgrades around which plugins are ready, and being ready first is worth more than any feature you could add that week.