Paper Plugin Guide
File mode0

Going further

Paper plugins: paper-plugin.yml, bootstrapper and loader

The newer plugin format, what the bootstrapper and loader do, and when to choose it.

Advanced29 min read

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:

Server console
> 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:

paper-plugin.ymlCompiles on Paper 26.3Compile Lab
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
    • 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

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.
  1. The same as in plugin.yml: the full name of your main class.
  2. Quoted, like always. A Paper plugin needs the version of Minecraft it was written for.
MinimalPlugin.javaCompiles on Paper 26.3Compile Lab
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
    • 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

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}
  1. The command is created in code. A paper-plugin.yml has no commands: section at all.
  2. A lambda: a tiny method written in place. BasicCommand has only one required method, so a lambda is enough. See Commands, part 1.
  3. 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:

FieldWhat it does
name, version, main, api-versionRequired. The same meaning as in plugin.yml.
description, authors, contributors, websiteInformation shown by commands like /version.
prefixThe text in brackets in front of your log lines. Defaults to the plugin name.
providesOther plugin names your plugin can stand in for.
bootstrapperOptional class that runs before the server loads. See below.
loaderOptional class that builds your classpath. See below.
dependenciesWhich other plugins you need and in what order. See next section.
has-open-classloaderAdvanced: 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:

paper-plugin.yml
1dependencies:2  server:3    Vault:4      load: BEFORE5      required: true6      join-classpath: true7    PlaceholderAPI:8      load: BEFORE9      required: false10      join-classpath: true
  1. Dependencies for your plugin itself. There is also a bootstrap: section for plugins your bootstrapper needs before the server starts.
  2. The other plugin's exact name, capital letters included.
  3. The other plugin starts before yours. AFTER means it starts after yours. OMIT says "no particular order".
  4. If it is missing, your plugin does not load. This is Vault's depend. With false your plugin loads anyway, like softdepend.
  5. Your plugin may use the other plugin's classes in your code. See the next section.
In plugin.ymlIn 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:

What happens when a Paper plugin starts, in order 1 Loader classloader() before anything 2 Bootstrap bootstrap() no worlds yet 3 Server loads worlds and the game 4 Plugin createPlugin() onLoad() 5 Plugin onEnable() ready to play Paper plugin only Steps 1 and 2 need a loader and a bootstrapper class in paper-plugin.yml Every plugin, with plugin.yml or paper-plugin.yml Steps 4 and 5 are the lifecycle you already know. Both classes are optional. In steps 1 and 2 most of the Bukkit API does not work yet. Use only what the bootstrap context gives you.
The loader and the bootstrapper run before the server loads its worlds. The plugin class itself is created afterwards.

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.COMMANDS event.
  • 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:

DemoBootstrap.javaCompiles on Paper 26.3Compile Lab
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
    • 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

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}
  1. 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.
  2. The interface Paper looks for. The class named in bootstrapper: must implement it.
  3. Paper calls this once, very early. The context argument is your toolbox for this phase.
  4. 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.
  5. Lifecycle events are Paper's "this just reached a milestone" hooks. Registering for COMMANDS says "call me when it is time to register commands".
  6. Adds the command. The registrar also works with Brigadier command trees, which is what BootCheckCommand builds.
  7. 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.
  8. Hands the information gathered during the bootstrap to the plugin. A normal plugin class could never have a constructor with arguments.
BootCheckCommand.javaCompiles on Paper 26.3Compile Lab
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
    • 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

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}
  1. Starts a command tree with the word bootcheck. Commands, part 2 explains these trees.
  2. Inserts the number into the message as plain text, which is the safe way to put values into MiniMessage.
  3. The number 1, which tells Brigadier "the command worked".
BootInfo.javaCompiles on Paper 26.3Compile Lab
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
    • 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

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}
  1. 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:

DemoPlugin.javaCompiles on Paper 26.3Compile Lab
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
    • 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

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}
  1. Not public: only the bootstrapper in the same package needs to call it.
DemoPlugin.javaCompiles on Paper 26.3Compile Lab
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
    • 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

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}
  1. How long ago the bootstrapper ran, so it shows how early that step happens.
  2. 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.

DemoLoader.javaCompiles on Paper 26.3Compile Lab
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
    • 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

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}
  1. The one method of PluginLoader. The builder lets you add things to your plugin's classpath.
  2. 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.
  3. 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.
  4. The library as group:name:version. Paper downloads it and everything it depends on.
  5. Hands the finished resolver to the builder. The downloaded jars are cached in the server's libraries folder, 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:

Server 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: true

To 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:

build.gradle.kts
1dependencies {2    compileOnly("com.github.ben-manes.caffeine:caffeine:3.2.0")3}
Using the downloaded library (Caffeine, for reference)
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:

paper-plugin.ymlCompiles on Paper 26.3Compile Lab
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
    • 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

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
  1. Points at the class that implements PluginBootstrap.
  2. Points at the class that implements PluginLoader. Leave both lines out and Paper just starts your main class.
  3. 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

Start the server and type bootcheck in the console. You see how long ago the bootstrapper ran:

Server console
> bootcheck[12:12:22 INFO]: The bootstrapper ran 14 seconds ago.

Which format should you choose?

plugin.ymlpaper-plugin.yml
Runs onPaper and, for pure Bukkit API plugins, Spigot tooPaper only
CommandsIn the file or in codeIn code only
Dependenciesdepend, softdepend, loadbeforeOne dependencies section
Other plugins' classesVisible to everyoneOnly for declared dependencies with join-classpath: true
Run code before worlds loadBarely (onLoad)Yes, with a bootstrapper
Download librarieslibraries: from Maven CentralA loader with any repository
Examples and tutorials onlineAlmost all of themFew

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 to paper-plugin.yml. It does nothing. Register commands in code.
  • Using the Bukkit API in bootstrap. The server is not ready, so things like Bukkit.getWorld or Bukkit.getOnlinePlayers fail or return nothing. Do the work in onEnable instead.
  • Forgetting the dependency block for a plugin whose classes you use, which leads to NoClassDefFoundError because of the isolated classloaders.
  • A wrong class name in main, bootstrapper or loader. 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.
Try it

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:

paper-plugin.ymlCompiles on Paper 26.3Compile Lab
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
    • 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

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.
HelloBootstrap.javaCompiles on Paper 26.3Compile Lab
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
    • 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

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}
  1. The command name, a short description for help lists, and the code to run, written as a lambda.
  2. 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.

Try it

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.

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
paper-plugin.yml
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: false

Essentials 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.yml is Paper's newer descriptor. A plugin uses it or plugin.yml, never both.
  • It has no commands: section, so commands go in code. Dependencies move into one dependencies section with load, required and join-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 MavenLibraryResolver and 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. Choose paper-plugin.yml only when you need these extra abilities.

Quick quiz

  1. A Paper plugin needs to run code before the game's worlds are loaded. Which class does that?

  2. Which paper-plugin.yml settings match softdepend: [Vault] in a plugin.yml?

  3. Your Paper plugin calls classes from another plugin and fails with NoClassDefFoundError, although that plugin is installed. What is the likely cause?

  4. Why should a loader use MavenLibraryResolver.MAVEN_CENTRAL_DEFAULT_MIRROR instead of Maven Central's own address?

  5. You write commands: with a command in your paper-plugin.yml. What happens?

Next steps