Paper plugins: paper-plugin.yml, bootstrapper and loader
The newer plugin format, what the bootstrapper and loader do, and when to choose it.
Besides the classic plugin.yml, Paper understands a newer descriptor called paper-plugin.yml. It adds two special startup classes, a bootstrapper and a loader, and a stricter way to declare dependencies. On this page you will use all three, and learn when the extra power is worth it.
Two formats, one server
A descriptor is the small file inside your jar that tells the server what the plugin is. You already know plugin.yml from the plugin.yml chapter. Paper adds a second descriptor, paper-plugin.yml. A plugin has one or the other, never both. Both kinds run side by side on the same server, and you can see them listed separately:
> plugins[12:12:21 INFO]: Server Plugins (8):[12:12:21 INFO]: Paper Plugins (3):[12:12:21 INFO]: - PaperBootstrapDemo, PaperHelloBootstrap, PaperMinimal[12:12:21 INFO]: Bukkit Plugins (5):[12:12:21 INFO]: - CoinsApi, PresenceCheck, ScoreboardsDemo, ScoreboardsExercise, ScoreboardsFirstSidebar"Paper Plugins" are the ones with paper-plugin.yml. "Bukkit Plugins" use plugin.yml. Your Java code can look exactly the same in both, because both still extend JavaPlugin. The differences are in the descriptor and in what you can set up around your main class.
The smallest paper-plugin.yml
The smallest working Paper plugin is a main class and a descriptor with the four fields you know:
Where this file livespaper-plugins-bootstrap-minimalsrcmainresourcespaper-plugin.yml
Files in src/main/resources are copied into the jar exactly as they are. Paper looks for plugin.yml at the top of the jar.
- paper-plugins-bootstrap-minimal/
- src/main/
- java/com/example/paperpluginsbootstrapminimal/Package com.example.paperpluginsbootstrapminimal
- MinimalPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- paper-plugin.ymlyou are hereTells Paper about a Paper plugin: name, version, main class, bootstrapper
- java/com/example/paperpluginsbootstrapminimal/Package com.example.paperpluginsbootstrapminimal
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1name: PaperMinimal2version: '1.0.0'3main: com.example.paperpluginsbootstrapminimal.MinimalPlugin4api-version: '26.3'5description: The smallest plugin that uses paper-plugin.yml instead of plugin.yml.- The same as in
plugin.yml: the full name of your main class. - Quoted, like always. A Paper plugin needs the version of Minecraft it was written for.
Where this file livespaper-plugins-bootstrap-minimalsrcmainjavacomexamplepaperpluginsbootstrapminimalMinimalPlugin.java
The package com.example.paperpluginsbootstrapminimal is the folder path com/example/paperpluginsbootstrapminimal inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- paper-plugins-bootstrap-minimal/
- src/main/
- java/com/example/paperpluginsbootstrapminimal/Package com.example.paperpluginsbootstrapminimal
- MinimalPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- paper-plugin.ymlTells Paper about a Paper plugin: name, version, main class, bootstrapper
- java/com/example/paperpluginsbootstrapminimal/Package com.example.paperpluginsbootstrapminimal
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
7@Override8public void onEnable() {9 registerCommand("papermini", "Says hello from a Paper plugin", (source, args) ->10 source.getSender().sendRichMessage("<green>Hello from a paper-plugin.yml plugin!"));11 getComponentLogger().info("PaperMinimal is ready.");12}- The command is created in code. A
paper-plugin.ymlhas nocommands:section at all. - A
lambda : a tiny method written in place.BasicCommandhas only one required method, so a lambda is enough. See Commands, part 1. - The logger for Adventure components. Its messages appear in the console with your plugin's name in brackets.
The fields you can use in paper-plugin.yml are:
| Field | What it does |
|---|---|
name, version, main, api-version | Required. The same meaning as in plugin.yml. |
description, authors, contributors, website | Information shown by commands like /version. |
prefix | The text in brackets in front of your log lines. Defaults to the plugin name. |
provides | Other plugin names your plugin can stand in for. |
bootstrapper | Optional class that runs before the server loads. See below. |
loader | Optional class that builds your classpath. See below. |
dependencies | Which other plugins you need and in what order. See next section. |
has-open-classloader | Advanced: lets other code reach into your plugin's classes. Leave it out. |
Declaring dependencies
In plugin.yml you wrote depend, softdepend and loadbefore as three lists of names. A Paper plugin replaces all three with one dependencies section. Each other plugin gets its own small block with three settings:
1dependencies:2 server:3 Vault:4 load: BEFORE5 required: true6 join-classpath: true7 PlaceholderAPI:8 load: BEFORE9 required: false10 join-classpath: true- Dependencies for your plugin itself. There is also a
bootstrap:section for plugins your bootstrapper needs before the server starts. - The other plugin's exact
name, capital letters included. - The other plugin starts before yours.
AFTERmeans it starts after yours.OMITsays "no particular order". - If it is missing, your plugin does not load. This is Vault's
depend. Withfalseyour plugin loads anyway, likesoftdepend. - Your plugin may use the other plugin's classes in your code. See the next section.
In plugin.yml | In paper-plugin.yml |
|---|---|
depend: [Vault] | load: BEFORE and required: true |
softdepend: [Vault] | load: BEFORE and required: false |
loadbefore: [Essentials] | load: AFTER |
Paper's own settings are required: true and join-classpath: true when you leave them out. Writing all three lines makes the file easier to read, so the examples always do.
Isolated classloaders
Here is the biggest behavior difference. A classloader is the part of Java that finds and loads the classes inside a jar. Plugins made with plugin.yml can all see each other's classes. Paper plugins are isolated: each one has its own classloader, and it cannot see another plugin's classes unless you declare that plugin as a dependency with join-classpath: true.
That is better for safety (two plugins that bundle different versions of the same library cannot clash), but it also explains a common error. If your Paper plugin calls into Vault and forgets to declare it, the class is not found, exactly like the NoClassDefFoundError in Working with other plugins. The fix is the dependency block above.
The startup order
The two special classes run at the very beginning of the startup, long before onEnable:
The bootstrapper
A bootstrapper is a class you write that implements PluginBootstrap. Paper runs it early, before the worlds and most of the game are ready. Its main job is registering things that must exist before the game finishes loading:
- Commands, with the
LifecycleEvents.COMMANDSevent. - Changes to registries, the game's lists of things like enchantments, damage types, paintings and cat variants, through
RegistryEvents. Custom enchantments are the most common reason people write a bootstrapper. - Datapacks your plugin brings along, with
LifecycleEvents.DATAPACK_DISCOVERY.
The example plugin paper-plugins-bootstrap-demo has a bootstrapper that does three things: logs a line, registers a command, and passes some information to the plugin. Here is the class:
Where this file livespaper-plugins-bootstrap-demosrcmainjavacomexamplepaperpluginsbootstrapdemoDemoBootstrap.java
The package com.example.paperpluginsbootstrapdemo is the folder path com/example/paperpluginsbootstrapdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- paper-plugins-bootstrap-demo/
- src/main/
- java/com/example/paperpluginsbootstrapdemo/Package com.example.paperpluginsbootstrapdemo
- BootCheckCommand.javaCommand (Brigadier tree)
- BootInfo.javaRecord: a small data class
- DemoBootstrap.javayou are hereBootstrapper: runs before the server loads worlds
- DemoLoader.javaLoader: adds libraries before the plugin starts
- DemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- paper-plugin.ymlTells Paper about a Paper plugin: name, version, main class, bootstrapper
- java/com/example/paperpluginsbootstrapdemo/Package com.example.paperpluginsbootstrapdemo
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1package com.example.paperpluginsbootstrapdemo;2 3import io.papermc.paper.plugin.bootstrap.BootstrapContext;4import io.papermc.paper.plugin.bootstrap.PluginBootstrap;5import io.papermc.paper.plugin.bootstrap.PluginProviderContext;6import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;7import java.time.Instant;8import org.bukkit.plugin.java.JavaPlugin;9 10@SuppressWarnings("UnstableApiUsage")11public final class DemoBootstrap implements PluginBootstrap {12 13 private BootInfo bootInfo;14 15 @Override16 public void bootstrap(BootstrapContext context) {17 bootInfo = new BootInfo(Instant.now());18 context.getLogger().info("Bootstrapping {} v{}",19 context.getConfiguration().getName(), context.getConfiguration().getVersion());20 21 context.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event ->22 event.registrar().register(BootCheckCommand.create(bootInfo), "Shows when the bootstrapper ran"));23 }24 25 @Override26 public JavaPlugin createPlugin(PluginProviderContext context) {27 return new DemoPlugin(bootInfo);28 }29}- IntelliJ marks the bootstrap and loader API as "unstable" because Paper may still change it. This silences the yellow warning; it does not change how the code runs.
- The interface Paper looks for. The class named in
bootstrapper:must implement it. - Paper calls this once, very early. The
contextargument is your toolbox for this phase. - The bootstrap context has its own logger, because
getLogger()on a plugin does not exist yet. The{}parts are filled with the values after the text. - Lifecycle events are Paper's "this just reached a milestone" hooks. Registering for
COMMANDSsays "call me when it is time to register commands". - Adds the command. The registrar also works with Brigadier command trees, which is what
BootCheckCommandbuilds. - Paper asks this method for your main class object. By default Paper builds it itself. Overriding it lets you give the plugin extra data through its constructor.
- Hands the information gathered during the bootstrap to the plugin. A normal plugin class could never have a constructor with arguments.
Where this file livespaper-plugins-bootstrap-demosrcmainjavacomexamplepaperpluginsbootstrapdemoBootCheckCommand.java
The package com.example.paperpluginsbootstrapdemo is the folder path com/example/paperpluginsbootstrapdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- paper-plugins-bootstrap-demo/
- src/main/
- java/com/example/paperpluginsbootstrapdemo/Package com.example.paperpluginsbootstrapdemo
- BootCheckCommand.javayou are hereCommand (Brigadier tree)
- BootInfo.javaRecord: a small data class
- DemoBootstrap.javaBootstrapper: runs before the server loads worlds
- DemoLoader.javaLoader: adds libraries before the plugin starts
- DemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- paper-plugin.ymlTells Paper about a Paper plugin: name, version, main class, bootstrapper
- java/com/example/paperpluginsbootstrapdemo/Package com.example.paperpluginsbootstrapdemo
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
14static LiteralCommandNode<CommandSourceStack> create(BootInfo bootInfo) {15 return Commands.literal("bootcheck")16 .executes(context -> {17 long seconds = bootInfo.timeSinceBootstrap().toSeconds();18 context.getSource().getSender().sendRichMessage(19 "<gray>The bootstrapper ran <yellow><seconds></yellow> seconds ago.",20 Placeholder.unparsed("seconds", Long.toString(seconds)));21 return Command.SINGLE_SUCCESS;22 })23 .build();24}- Starts a command tree with the word
bootcheck. Commands, part 2 explains these trees. - Inserts the number into the message as plain text, which is the safe way to put values into MiniMessage.
- The number 1, which tells Brigadier "the command worked".
Where this file livespaper-plugins-bootstrap-demosrcmainjavacomexamplepaperpluginsbootstrapdemoBootInfo.java
The package com.example.paperpluginsbootstrapdemo is the folder path com/example/paperpluginsbootstrapdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- paper-plugins-bootstrap-demo/
- src/main/
- java/com/example/paperpluginsbootstrapdemo/Package com.example.paperpluginsbootstrapdemo
- BootCheckCommand.javaCommand (Brigadier tree)
- BootInfo.javayou are hereRecord: a small data class
- DemoBootstrap.javaBootstrapper: runs before the server loads worlds
- DemoLoader.javaLoader: adds libraries before the plugin starts
- DemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- paper-plugin.ymlTells Paper about a Paper plugin: name, version, main class, bootstrapper
- java/com/example/paperpluginsbootstrapdemo/Package com.example.paperpluginsbootstrapdemo
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1package com.example.paperpluginsbootstrapdemo;2 3import java.time.Duration;4import java.time.Instant;5 6record BootInfo(Instant bootstrappedAt) {7 8 Duration timeSinceBootstrap() {9 return Duration.between(bootstrappedAt, Instant.now());10 }11}- A
record is a short way to write a class that only holds values. This one remembers when the bootstrapper ran.
The plugin class receives that object in its constructor and uses it in onEnable:
Where this file livespaper-plugins-bootstrap-demosrcmainjavacomexamplepaperpluginsbootstrapdemoDemoPlugin.java
The package com.example.paperpluginsbootstrapdemo is the folder path com/example/paperpluginsbootstrapdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- paper-plugins-bootstrap-demo/
- src/main/
- java/com/example/paperpluginsbootstrapdemo/Package com.example.paperpluginsbootstrapdemo
- BootCheckCommand.javaCommand (Brigadier tree)
- BootInfo.javaRecord: a small data class
- DemoBootstrap.javaBootstrapper: runs before the server loads worlds
- DemoLoader.javaLoader: adds libraries before the plugin starts
- DemoPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- paper-plugin.ymlTells Paper about a Paper plugin: name, version, main class, bootstrapper
- java/com/example/paperpluginsbootstrapdemo/Package com.example.paperpluginsbootstrapdemo
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
9DemoPlugin(BootInfo bootInfo) {10 this.bootInfo = bootInfo;11}- Not public: only the bootstrapper in the same package needs to call it.
Where this file livespaper-plugins-bootstrap-demosrcmainjavacomexamplepaperpluginsbootstrapdemoDemoPlugin.java
The package com.example.paperpluginsbootstrapdemo is the folder path com/example/paperpluginsbootstrapdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- paper-plugins-bootstrap-demo/
- src/main/
- java/com/example/paperpluginsbootstrapdemo/Package com.example.paperpluginsbootstrapdemo
- BootCheckCommand.javaCommand (Brigadier tree)
- BootInfo.javaRecord: a small data class
- DemoBootstrap.javaBootstrapper: runs before the server loads worlds
- DemoLoader.javaLoader: adds libraries before the plugin starts
- DemoPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- paper-plugin.ymlTells Paper about a Paper plugin: name, version, main class, bootstrapper
- java/com/example/paperpluginsbootstrapdemo/Package com.example.paperpluginsbootstrapdemo
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
13@Override14public void onEnable() {15 getComponentLogger().info("Enabled {} ms after the bootstrapper ran.",16 bootInfo.timeSinceBootstrap().toMillis());17 getComponentLogger().info("Caffeine library available: {}",18 isClassAvailable("com.github.benmanes.caffeine.cache.Caffeine"));19}- How long ago the bootstrapper ran, so it shows how early that step happens.
- Checks whether the library from the loader really arrived. See the next section.
The loader and runtime libraries
Sometimes your plugin needs a library that the server does not have, such as a caching library or a database driver. You could pack it into your jar, which makes the jar big. A loader offers a cleaner way: it tells Paper which libraries to download when the server starts, and Paper adds them to your plugin's classpath. The classpath is the list of places where Java looks for classes.
Where this file livespaper-plugins-bootstrap-demosrcmainjavacomexamplepaperpluginsbootstrapdemoDemoLoader.java
The package com.example.paperpluginsbootstrapdemo is the folder path com/example/paperpluginsbootstrapdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- paper-plugins-bootstrap-demo/
- src/main/
- java/com/example/paperpluginsbootstrapdemo/Package com.example.paperpluginsbootstrapdemo
- BootCheckCommand.javaCommand (Brigadier tree)
- BootInfo.javaRecord: a small data class
- DemoBootstrap.javaBootstrapper: runs before the server loads worlds
- DemoLoader.javayou are hereLoader: adds libraries before the plugin starts
- DemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- paper-plugin.ymlTells Paper about a Paper plugin: name, version, main class, bootstrapper
- java/com/example/paperpluginsbootstrapdemo/Package com.example.paperpluginsbootstrapdemo
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
13@Override14public void classloader(PluginClasspathBuilder classpathBuilder) {15 MavenLibraryResolver resolver = new MavenLibraryResolver();16 resolver.addRepository(new RemoteRepository.Builder(17 "central", "default", MavenLibraryResolver.MAVEN_CENTRAL_DEFAULT_MIRROR).build());18 resolver.addDependency(new Dependency(19 new DefaultArtifact("com.github.ben-manes.caffeine:caffeine:3.2.0"), null));20 classpathBuilder.addLibrary(resolver);21}- The one method of
PluginLoader. The builder lets you add things to your plugin's classpath. - A helper that downloads libraries from a Maven
repository , the same kind of server Gradle uses. A new resolver starts with no repositories, not even Maven Central. - Maven Central's address for Paper servers. Use this constant instead of typing Maven Central's own URL: Maven Central asks programs not to use it as a download service for every server start, and the mirror exists for exactly this.
- The library as
group:name:version. Paper downloads it and everything it depends on. - Hands the finished resolver to the builder. The downloaded jars are cached in the server's
librariesfolder, so the second start is fast.
The loader runs even before the bootstrapper. On the first start you can watch the download in the console:
[12:12:05 INFO]: [PluginInitializerManager] Initializing plugins...[12:12:06 INFO]: [MavenLibraryResolver] Downloading https://maven-central.storage-download.googleapis.com/maven2/com/github/ben-manes/caffeine/caffeine/3.2.0/caffeine-3.2.0.jar[12:12:07 INFO]: [PluginInitializerManager] Initialized 8 plugins[12:12:08 INFO]: [PaperBootstrapDemo] Bootstrapping PaperBootstrapDemo v1.0.0[12:12:20 INFO]: [PaperBootstrapDemo] Enabling PaperBootstrapDemo v1.0.0[12:12:20 INFO]: [PaperBootstrapDemo] Enabled 12902 ms after the bootstrapper ran.[12:12:20 INFO]: [PaperBootstrapDemo] Caffeine library available: trueTo use the library in your own code, also add it to build.gradle.kts as compileOnly, so your code compiles but Gradle does not pack it into the jar:
1dependencies {2 compileOnly("com.github.ben-manes.caffeine:caffeine:3.2.0")3}1Cache<UUID, Integer> kills = Caffeine.newBuilder()2 .expireAfterWrite(Duration.ofMinutes(10))3 .build();Putting it together
This is the whole demo plugin. The descriptor ties the three classes together:
Where this file livespaper-plugins-bootstrap-demosrcmainresourcespaper-plugin.yml
Files in src/main/resources are copied into the jar exactly as they are. Paper looks for plugin.yml at the top of the jar.
- paper-plugins-bootstrap-demo/
- src/main/
- java/com/example/paperpluginsbootstrapdemo/Package com.example.paperpluginsbootstrapdemo
- BootCheckCommand.javaCommand (Brigadier tree)
- BootInfo.javaRecord: a small data class
- DemoBootstrap.javaBootstrapper: runs before the server loads worlds
- DemoLoader.javaLoader: adds libraries before the plugin starts
- DemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- paper-plugin.ymlyou are hereTells Paper about a Paper plugin: name, version, main class, bootstrapper
- java/com/example/paperpluginsbootstrapdemo/Package com.example.paperpluginsbootstrapdemo
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1name: PaperBootstrapDemo2version: '1.0.0'3main: com.example.paperpluginsbootstrapdemo.DemoPlugin4bootstrapper: com.example.paperpluginsbootstrapdemo.DemoBootstrap5loader: com.example.paperpluginsbootstrapdemo.DemoLoader6api-version: '26.3'7description: A bootstrapper that registers a command early and a loader that downloads a library.8authors: [Guide]9dependencies:10 server:11 LuckPerms:12 load: BEFORE13 required: false14 join-classpath: true- Points at the class that implements
PluginBootstrap. - Points at the class that implements
PluginLoader. Leave both lines out and Paper just starts your main class. - An optional dependency. If LuckPerms is installed it starts first and the plugin may use its classes; if not, the plugin loads without it.
- paper-plugins-bootstrap-demo/
- src/main/
- java/com/example/paperpluginsbootstrapdemo/
- DemoLoader.javaRuns first: adds the Caffeine library to the classpath
- DemoBootstrap.javaRuns second: registers /bootcheck and creates the main class
- BootCheckCommand.javaThe Brigadier command tree for /bootcheck
- BootInfo.javaA record with the time the bootstrapper ran
- DemoPlugin.javaThe main class, which receives the BootInfo
- resources/
- paper-plugin.ymlThe descriptor that names all of the above
- java/com/example/paperpluginsbootstrapdemo/
- src/main/
Start the server and type bootcheck in the console. You see how long ago the bootstrapper ran:
> bootcheck[12:12:22 INFO]: The bootstrapper ran 14 seconds ago.Which format should you choose?
plugin.yml | paper-plugin.yml | |
|---|---|---|
| Runs on | Paper and, for pure Bukkit API plugins, Spigot too | Paper only |
| Commands | In the file or in code | In code only |
| Dependencies | depend, softdepend, loadbefore | One dependencies section |
| Other plugins' classes | Visible to everyone | Only for declared dependencies with join-classpath: true |
| Run code before worlds load | Barely (onLoad) | Yes, with a bootstrapper |
| Download libraries | libraries: from Maven Central | A loader with any repository |
| Examples and tutorials online | Almost all of them | Few |
A short rule: start with plugin.yml. Switch to paper-plugin.yml when you need custom registry entries, a bootstrapper, a loader with your own repository, or you want Paper's stricter isolation. Do not switch just because it is newer. A plugin with plugin.yml keeps working on Paper, and you lose nothing for ordinary plugins.
Mistakes to avoid
- Having both descriptors. A jar needs one. Pick a format and delete the other file.
- Adding a
commands:section topaper-plugin.yml. It does nothing. Register commands in code. - Using the Bukkit API in
bootstrap. The server is not ready, so things likeBukkit.getWorldorBukkit.getOnlinePlayersfail or return nothing. Do the work inonEnableinstead. - Forgetting the dependency block for a plugin whose classes you use, which leads to
NoClassDefFoundErrorbecause of the isolated classloaders. - A wrong class name in
main,bootstrapperorloader. These are full names with the package, spelled exactly like the class. - Typing Maven Central's URL into your loader. Use
MavenLibraryResolver.MAVEN_CENTRAL_DEFAULT_MIRROR. - Reaching for these tools too early. Most plugin ideas, including every project in this guide, work with a plain
plugin.yml.
Register /hello from the bootstrapper
Create a Paper plugin with a bootstrapper that registers a /hello command. It should greet the sender by name. The main class only needs to log one line.
Hint 1
Copy the structure of DemoBootstrap. You do not need a loader or createPlugin. Leave out the loader: line in the descriptor.
Hint 2
The registrar has a register method that takes a command name, a description and a BasicCommand. Get the sender's name with source.getSender().getName().
Show the solution
The descriptor names the bootstrapper, and the bootstrapper registers the command during the COMMANDS event:
Where this file livespaper-plugins-bootstrap-exercisesrcmainresourcespaper-plugin.yml
Files in src/main/resources are copied into the jar exactly as they are. Paper looks for plugin.yml at the top of the jar.
- paper-plugins-bootstrap-exercise/
- src/main/
- java/com/example/paperpluginsbootstrapexercise/Package com.example.paperpluginsbootstrapexercise
- HelloBootstrap.javaBootstrapper: runs before the server loads worlds
- HelloPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- paper-plugin.ymlyou are hereTells Paper about a Paper plugin: name, version, main class, bootstrapper
- java/com/example/paperpluginsbootstrapexercise/Package com.example.paperpluginsbootstrapexercise
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
1name: PaperHelloBootstrap2version: '1.0.0'3main: com.example.paperpluginsbootstrapexercise.HelloPlugin4bootstrapper: com.example.paperpluginsbootstrapexercise.HelloBootstrap5api-version: '26.3'6description: Registers /hello from the bootstrapper.Where this file livespaper-plugins-bootstrap-exercisesrcmainjavacomexamplepaperpluginsbootstrapexerciseHelloBootstrap.java
The package com.example.paperpluginsbootstrapexercise is the folder path com/example/paperpluginsbootstrapexercise inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.
- paper-plugins-bootstrap-exercise/
- src/main/
- java/com/example/paperpluginsbootstrapexercise/Package com.example.paperpluginsbootstrapexercise
- HelloBootstrap.javayou are hereBootstrapper: runs before the server loads worlds
- HelloPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- paper-plugin.ymlTells Paper about a Paper plugin: name, version, main class, bootstrapper
- java/com/example/paperpluginsbootstrapexercise/Package com.example.paperpluginsbootstrapexercise
- build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
- gradle.propertiesVersion numbers used by the build
- gradlew.batRuns Gradle on Windows without installing it
- settings.gradle.ktsThe project's name
- src/main/
Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.
11@Override12public void bootstrap(BootstrapContext context) {13 context.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event ->14 event.registrar().register("hello", "Greets you by name", (source, args) ->15 source.getSender().sendRichMessage(16 "<green>Hello, <white><name></white>!",17 Placeholder.unparsed("name", source.getSender().getName()))));18}- The command name, a short description for help lists, and the code to run, written as a lambda.
- The name of whoever typed the command. The console's name is
CONSOLE.
The main class is HelloPlugin with an onEnable that only writes to the log.
Convert the dependencies
This plugin.yml belongs to a plugin that you want to turn into a Paper plugin. Write the dependencies section of the new paper-plugin.yml.
1depend: [Vault]2softdepend: [PlaceholderAPI]3loadbefore: [Essentials]Hint
Each plugin gets its own block under server: with load, required and join-classpath. Essentials has to start after yours.
Show the solution
1dependencies:2 server:3 Vault:4 load: BEFORE5 required: true6 join-classpath: true7 PlaceholderAPI:8 load: BEFORE9 required: false10 join-classpath: true11 Essentials:12 load: AFTER13 required: false14 join-classpath: falseEssentials uses AFTER because it must load after your plugin, and required: false because your plugin does not need it. join-classpath: false says you never use Essentials's classes in your code.
Recap
paper-plugin.ymlis Paper's newer descriptor. A plugin uses it orplugin.yml, never both.- It has no
commands:section, so commands go in code. Dependencies move into onedependenciessection withload,requiredandjoin-classpath. - Paper plugins run in isolated classloaders. To use another plugin's classes, declare it as a dependency with
join-classpath: true. - The loader runs first and builds the classpath, for example by downloading libraries with
MavenLibraryResolverand the default Maven Central mirror. - The bootstrapper runs before the worlds load. Use it for commands, registry changes and datapacks, and pass data to your plugin through
createPlugin. Most of the Bukkit API does not work there yet. - Start with
plugin.yml. Choosepaper-plugin.ymlonly when you need these extra abilities.
Quick quiz
A Paper plugin needs to run code before the game's worlds are loaded. Which class does that?
The bootstrapper is built for early startup work like registering commands and registry entries. The loader only prepares the classpath. A join listener runs only when a player connects, long after the worlds exist.Which
paper-plugin.ymlsettings matchsoftdepend: [Vault]in aplugin.yml?A soft dependency starts first when it exists (BEFORE) and is not mandatory (required: false).AFTERwould make Vault start after your plugin.Your Paper plugin calls classes from another plugin and fails with
NoClassDefFoundError, although that plugin is installed. What is the likely cause?Each Paper plugin sees only its own classes plus the dependencies it joins. Declaring the dependency fixes the lookup.Why should a loader use
MavenLibraryResolver.MAVEN_CENTRAL_DEFAULT_MIRRORinstead of Maven Central's own address?Paper provides the mirror so thousands of servers do not hit Maven Central directly. Other repositories can still be added withaddRepository.You write
commands:with a command in yourpaper-plugin.yml. What happens?Paper plugins register commands withregisterCommandor Brigadier. The YAML section is simply not read.
Next steps
- Registries, keys and data components: what the game's registries are, and what the bootstrapper can add to them.
- Commands, part 2: the command trees you register from a bootstrapper.
- Server internals (NMS) with paperweight: another advanced topic that uses a Paper plugin setup.