This document covers maintenance tasks that are easy to miss when adding messages or updating Paper reload support.
Messages must work for new installations and existing installations with customized files.
- Add the key to every bundled resource file:
plugman-core/src/main/resources/messages.ymlplugman-core/src/main/resources/messages_de.ymlplugman-core/src/main/resources/messages_es.ymlplugman-core/src/main/resources/messages_cn.ymlplugman-core/src/main/resources/messages_jp.ymlplugman-core/src/main/resources/messages_ru.ymlplugman-core/src/main/resources/messages_tw.yml
- Add the default text to the appropriate defaults record in
MessageMigrationService. General messages useMessageDefaults; bulk reload messages currently useBulkMessageDefaults. - Register the key in
MessageMigrationService#addMissingEntrieswithaddMissingMessageEntry. UseaddMissingNestedMessageEntryonly for nested sections such aserror.usage. - Keep placeholder indexes identical in every language. For example,
{0}and{1}must have the same meaning everywhere. - Do not replace an existing key during migration. The migration service must only backfill missing entries so customized messages remain untouched.
PlugManConfigurationManager runs the message migration during normal configuration loading, including for users already on the current config version. A config version bump is therefore not required when only adding message keys.
When a new command is added, also check its help message, permission in plugin.yml, command handler registration, tab completion, README command table, and all bundled language files.
Paper plugin loading and unloading uses internal APIs that may change without compatibility guarantees. Prefer runtime capability checks and signature-based reflection over a Minecraft version check whenever possible.
Paper uses different JARs for its public API and its internal server implementation. Check the JAR that actually owns the changed class before modifying PlugManX.
The public API is normally stored in the local Maven repository:
Windows:
%USERPROFILE%\.m2\repository\io\papermc\paper\paper-api\<version>\paper-api-<version>.jar
Linux:
~/.m2/repository/io/papermc/paper/paper-api/<version>/paper-api-<version>.jar
This JAR contains supported types such as events, lifecycle API interfaces, command APIs, plugin metadata interfaces, and general Bukkit/Paper API classes.
Search all cached Paper API versions on Windows with PowerShell:
Get-ChildItem "$env:USERPROFILE\.m2\repository\io\papermc\paper\paper-api" -Recurse -Filter "*.jar"Search them on Linux:
find "$HOME/.m2/repository/io/papermc/paper/paper-api" -type f -name '*.jar'PlugManX reload support also uses Paper implementation classes that are not included in paper-api. The Paper launcher usually creates the full runtime JAR inside the server directory:
Windows:
<server>\versions\<version>\paper-<version>.jar
Linux:
<server>/versions/<version>/paper-<version>.jar
For example on Windows:
versions\26.2\paper-26.2.jar
On Linux:
versions/26.2/paper-26.2.jar
Use this generated runtime JAR for provider storage, plugin manager implementation, lifecycle internals, CraftBukkit, and NMS inspection. The small Paper launcher JAR in the server root may only bootstrap or patch the server and may not contain the final runtime classes.
Find possible runtime JARs from the server root with:
Windows PowerShell:
Get-ChildItem -Recurse -Filter "paper-*.jar"Linux:
find . -type f -name 'paper-*.jar'Common package locations are:
| Area | Package or class location |
|---|---|
| Paper plugin manager | io.papermc.paper.plugin.manager |
| Entrypoints | io.papermc.paper.plugin.entrypoint |
| Provider sources | io.papermc.paper.plugin.provider.source |
| Provider types | io.papermc.paper.plugin.provider.type |
| Provider storage | io.papermc.paper.plugin.storage |
| Lifecycle events | io.papermc.paper.plugin.lifecycle |
| Paper commands | io.papermc.paper.command.brigadier |
| CraftBukkit implementation | org.bukkit.craftbukkit |
| Minecraft server internals | net.minecraft |
| Recipe internals | net.minecraft.world.item.crafting |
List matching classes before guessing a package or nested class name:
Windows PowerShell:
jar tf .\versions\26.2\paper-26.2.jar | Select-String "FileProviderSource|ProviderStorage|RecipeMap"Linux:
jar tf ./versions/26.2/paper-26.2.jar | grep -E 'FileProviderSource|ProviderStorage|RecipeMap'Use javap from the same JDK that runs or builds the server to inspect fields, method signatures, and bytecode:
Windows PowerShell:
& "$env:JAVA_HOME\bin\javap.exe" -classpath .\versions\26.2\paper-26.2.jar -p `
io.papermc.paper.plugin.provider.source.FileProviderSource
& "$env:JAVA_HOME\bin\javap.exe" -classpath .\versions\26.2\paper-26.2.jar -p -c `
net.minecraft.world.item.crafting.RecipeManagerLinux:
"$JAVA_HOME/bin/javap" -classpath ./versions/26.2/paper-26.2.jar -p \
io.papermc.paper.plugin.provider.source.FileProviderSource
"$JAVA_HOME/bin/javap" -classpath ./versions/26.2/paper-26.2.jar -p -c \
net.minecraft.world.item.crafting.RecipeManagerUseful javap options:
-pshows private and package-private members.-cshows method bytecode and reveals which internal method is actually called.-sshows JVM descriptors, which helps distinguish overloaded methods.
For nested classes, use the exact name returned by jar tf, including $, and quote it in the shell:
Windows PowerShell:
& "$env:JAVA_HOME\bin\javap.exe" -classpath .\versions\26.2\paper-26.2.jar -p `
'io.papermc.paper.plugin.provider.type.paper.PaperPluginParent$PaperServerPluginProvider'Linux:
"$JAVA_HOME/bin/javap" -classpath ./versions/26.2/paper-26.2.jar -p \
'io.papermc.paper.plugin.provider.type.paper.PaperPluginParent$PaperServerPluginProvider'An IDE decompiler can be used for easier reading, but confirm reflection targets with javap -p -s against the actual runtime JAR. Paper source code and development bundles are useful references, but the generated JAR from the affected server is the final source of truth for a reported runtime failure.
When comparing Paper versions, run the same jar tf and javap commands against both runtime JARs. Record renamed classes, changed parameter types, new overloads, moved fields, and changed return types before adding a compatibility branch.
Assume a new Paper version produces this debug message during recipe cleanup:
NoSuchMethodException: location method not found in net.minecraft.resources.ResourceKey
-
Find the affected class in the runtime JAR.
Windows PowerShell:
jar tf .\versions\26.2\paper-26.2.jar | Select-String "net/minecraft/resources/ResourceKey"
Linux:
jar tf ./versions/26.2/paper-26.2.jar | grep 'net/minecraft/resources/ResourceKey'
-
Inspect the available methods in both the previously working JAR and the new JAR.
Windows PowerShell:
& "$env:JAVA_HOME\bin\javap.exe" -classpath .\versions\26.1\paper-26.1.jar -p -s net.minecraft.resources.ResourceKey & "$env:JAVA_HOME\bin\javap.exe" -classpath .\versions\26.2\paper-26.2.jar -p -s net.minecraft.resources.ResourceKey
Linux:
"$JAVA_HOME/bin/javap" -classpath ./versions/26.1/paper-26.1.jar -p -s net.minecraft.resources.ResourceKey "$JAVA_HOME/bin/javap" -classpath ./versions/26.2/paper-26.2.jar -p -s net.minecraft.resources.ResourceKey
-
Identify the smallest compatibility change. If
location()was replaced byidentifier(), keep support for both instead of checking only the Paper version:private String resourceKeyString(Object key) throws ReflectiveOperationException { try { return String.valueOf(invokeNoArgMethod(key, "location")); } catch (NoSuchMethodException ignored) { // Newer Paper versions expose the identifier under a different method name. } try { return String.valueOf(invokeNoArgMethod(key, "identifier")); } catch (NoSuchMethodException ignored) { return String.valueOf(key); } }
-
Log which branch was selected when
paperReloadDebugis enabled. This makes the next compatibility report easier to diagnose. -
Test the same build on both Paper versions. Verify the complete reload twice, not only the reflection call, because stale state often appears on the second reload.
The same process applies when a method changes parameters. For example, if FileProviderSource#prepareContext changes, inspect all overloads with javap -p -s, then attempt compatible signatures in a safe order such as Path, Object, and String. If the method is optional on an older supported version, keep a documented fallback instead of treating its absence as a fatal error.
Do not catch every reflection error and silently continue. Only NoSuchMethodException or another expected capability mismatch should select a fallback. Invocation failures from a method that was found should be logged and treated as a real load or unload failure.
PaperReflectionNames: Paper internal class and field names.PaperInitializer: selection of legacy or modern reload strategies.PaperPlugManLoader: Paper library-loader compatibility range.PaperPluginManager#loadPaperPluginWithProviderStorage: provider creation, registration, and enable flow.prepareProviderContextandregisterProviders:FileProviderSourcemethod signatures.invokeStorageRegister,invokeStorageEnter, andinvokeStorageProcessProvided: provider storage lifecycle.createDependencyTreeandinvokeDependencyTreeAdd: dependency-tree method names and overloads.- Paper command lifecycle handling and lifecycle-owner context setup.
PaperPluginManager#unloadWithPaper: common listeners, commands, permissions, recipes, and plugin-list cleanup.PaperPluginManager#cleanupPaperPluginManager: Paper instance-manager cleanup.ModernPaperPluginManager: provider storage, provider caches, event executors, andSafeClassDefinercleanup.- Recipe cleanup through Paper's internal
RecipeMap; field names and recipe key methods can change between releases.
- Reproduce the failure with
paperReloadDebug: trueand record the failing step and complete exception. - Compare the affected Paper classes and method signatures with the last supported version.
- Update
PaperReflectionNameswhen a class or field moved. - Add a runtime capability branch for changed methods or overloads. Keep the older branch while its Paper versions remain supported.
- Match methods by compatible parameter types, not only by method name, when Paper exposes multiple overloads.
- Verify both sides of the lifecycle: anything added to provider storage, commands, listeners, recipes, caches, or classloader registries during load must be removed during unload.
- Keep failure behavior explicit. Once Paper provider loading starts, do not fall back to the Bukkit load path if that could register the same plugin twice.
- Only raise version gates such as
MAX_LIBRARY_LOADER_VERSIONafter the new Paper version has been tested. Do not assume an unchanged API from the version number alone. - Extend debug output for a new compatibility branch so future reports show which capability and overload were selected.
Test at least the oldest supported Paper release, Paper 1.21.8, the current stable release, and the upcoming release before expanding a version gate.
For each version, test both plugin.yml and paper-plugin.yml plugins with:
- Commands and Paper lifecycle commands
- Hard and soft dependencies, including
provides - Listeners, scheduler tasks, and services
- Custom recipes
- Repeated load, unload, reload, and restart operations
reload allwith dependency ordering- At least one online player, because command and recipe synchronization can follow a different and more expensive path
After unload, check that the unload leak detector reports no remaining tasks, services, listeners, commands, or plugin-owned threads. Also verify that a second load does not produce duplicate plugin, command, provider, lifecycle, or recipe registrations.
Run the complete reactor build after maintenance changes:
Windows PowerShell:
mvn.cmd clean package "-Dlicense.skipUpdateLicense=true"Linux:
mvn clean package -Dlicense.skipUpdateLicense=trueThe build must complete with all tests passing. The assembled server plugin is written to target/PlugManX.jar.