Plugin Basics
Understand how Nukkit-MOT recognizes, loads, and enables your plugin before you try to run it on a real server.
Step 2 of 4: understand plugin.yml, the main class, and the plugin lifecycle.
Previous: First Java Plugin
How Nukkit-MOT Loads a Plugin
When you place a jar into plugins/, the loading flow is roughly:
PluginManagerscans theplugins/directory for.jarfiles.JavaPluginLoaderreadsplugin.ymlfrom the jar root.- The loader looks up the class declared in
main. - That class is instantiated as a
PluginBase. onLoad()is called.- The plugin is enabled later and
onEnable()is called. - When the server stops or the plugin is disabled,
onDisable()is called.
If a required dependency from depend is missing, the plugin will not load.
Minimal Project Layout
For a normal Java plugin, keep this structure:
hello-world-plugin/
├─ pom.xml
└─ src/
└─ main/
├─ java/
│ └─ com/example/helloworld/HelloWorldPlugin.java
└─ resources/
└─ plugin.yml
Two beginner rules matter most:
- Put your Java entry class under
src/main/java/ - Put
plugin.ymlundersrc/main/resources/
Required plugin.yml Fields
According to the current PluginDescription implementation, these fields are required:
| Field | Required | Meaning |
|---|---|---|
name | Yes | Plugin name. It is also used for the plugin data folder name. |
main | Yes | Fully qualified entry class name, such as com.example.helloworld.HelloWorldPlugin. |
version | Yes | Plugin version string. Quote values like "1.0.0". |
api | Yes | Supported Nukkit API version string or list. A list such as ["1.0.0"] is the clearest style for new plugins. |
- Your main class must extend
PluginBase - Your main class should not be inside the
cn.nukkit.*package plugin.ymlmust end up at the jar root after packaging
Common Optional Fields
These fields are not required for a minimal HelloWorld plugin, but you will use them often:
| Field | Use |
|---|---|
author / authors | Show who maintains the plugin |
description | Short summary shown in metadata |
website | Project page, docs, or repository |
prefix | Prefix used by the plugin logger |
load | Load timing: STARTUP or POSTWORLD |
depend | Hard dependencies; missing ones block plugin loading |
softdepend | Optional dependencies; plugin still loads without them |
loadbefore | Hint that your plugin should load before another plugin |
libraries | Optional. External Maven coordinates (groupId:artifactId:version) that Nukkit-MOT downloads and isolates per plugin. Only list libraries your plugin code actually imports. See Auto-Resolve Maven Libraries. |
repositories | Optional. Extra repository URLs tried before the built-in defaults when resolving libraries. Each URL should end with /. Only needed for private Nexus/Artifactory or non-Maven-Central hosts; public coordinates resolve without it. |
commands | Command metadata registered from plugin.yml |
permissions | Permission nodes registered from plugin.yml |
load defaults to POSTWORLD. Use STARTUP only when your plugin really needs to register something before worlds or other registries finish loading.
A Complete plugin.yml Example
You only need the first four fields for the basic HelloWorld plugin, but a more realistic file often looks like this:
name: HelloWorldPlugin
main: com.example.helloworld.HelloWorldPlugin
version: "1.0.0"
api: ["1.0.0"]
author: YourName
description: A minimal example plugin for Nukkit-MOT
website: https://example.com
prefix: HelloWorld
load: POSTWORLD
softdepend:
- SomeOptionalPlugin
libraries:
- "com.squareup.okhttp3:okhttp:4.12.0"
- "org.xerial:sqlite-jdbc:3.45.1.0"
commands:
helloworld:
description: Send a hello message
usage: "/helloworld"
permission: helloworld.command
permissions:
helloworld.command:
description: Allows the player to use /helloworld
default: true
You can delete the commands and permissions sections until you are ready to add those features.
Main Class and Lifecycle
Your plugin entry class usually extends PluginBase and overrides one or more lifecycle methods:
package com.example.helloworld;
import cn.nukkit.plugin.PluginBase;
public final class HelloWorldPlugin extends PluginBase {
@Override
public void onLoad() {
this.getLogger().info("Plugin is loading");
}
@Override
public void onEnable() {
this.getLogger().info("Plugin is enabled");
}
@Override
public void onDisable() {
this.getLogger().info("Plugin is disabled");
}
}
Use them like this:
onLoad()for early lightweight initializationonEnable()for normal startup work such as registering listeners, commands, or tasksonDisable()for saving state and cleaning up resources
Do not put plugin startup logic in the constructor. Let the server control the lifecycle.
Data Folder and Resources
Nukkit-MOT creates a data folder for your plugin based on the plugin name, usually:
plugins/HelloWorldPlugin/
Files inside src/main/resources/ are bundled into the jar. That is why plugin.yml belongs there, and later your default config.yml will belong there too.
Auto-Resolve Maven Libraries (plugin.yml)
Instead of shading large third-party libraries into your plugin jar (and inflating its size), you can declare their Maven coordinates directly in plugin.yml. Nukkit-MOT downloads each library into a shared libraries/ folder under the server data path and attaches them to your plugin's own ClassLoader at load time.
libraries is optional. Every declared coordinate is downloaded and resolved (including transitive dependencies) on plugin load, slowing startup and consuming disk whether your code uses the library or not. Do not copy-paste a build file's full dependency list — add a coordinate only when your plugin code imports that library.
libraries:
- "com.squareup.okhttp3:okhttp:4.12.0"
- "org.xerial:sqlite-jdbc:3.45.1.0"
repositories:
- "https://maven.my-company.com/repository/public/"
- "https://jitpack.io"
How resolution works
For each groupId:artifactId:version entry, the server:
- Validates the coordinate and refuses anything that could escape the
libraries/folder (path traversal, leading/trailing dots, backslashes, control characters). - Tries each
repositoriesURL in order, then the built-in fallback repositories (Maven Central andrepo.lanink.cn). The first hit wins. - Downloads the
.jartolibraries/<group path>/<artifact>/<version>/<artifact>-<version>.jar, writing to a.tmpfile first and atomically moving it into place. - Reads the matching
.pomand recursively resolves transitive dependencies with anearest-winsstrategy (the first version seen for a givengroupId:artifactIdwins; conflicts are skipped, not promoted).
Already-downloaded artifacts are reused across plugins, so the cost is paid once per version.
ClassLoader isolation
Each plugin gets its own PluginClassLoader. When your plugin loads a class:
- The plugin's own URLs are checked first — this includes the main jar and every
librariesjar. - If the class is not found there, the loader falls back to the global scan so the existing
depend/softdependmechanism still works.
This means the version you declared in libraries keeps priority over a same-named class shaded inside another plugin. It is version-preference isolation, not access control — plugins can still see each other's classes through the global fallback.
What is and isn't supported
Supported:
- Root
<dependencies>with literal versions, scopecompileorruntime, andoptional != true. - Transitive resolution through those supported dependencies.
- Custom repositories declared via
repositories.
Not supported (the POM parser is intentionally minimal):
parentinheritance,dependencyManagement, BOM imports,exclusions,relocations.- Version ranges, classifiers, and
${...}property placeholders in POM<version>. provided,test,system, andimportscopes — these are filtered out.
If a library needs any of the above, declare the extra coordinates explicitly in libraries, or keep shading that dependency into your jar.
Failure modes
- A malformed coordinate (not exactly three
:-separated parts, or containing illegal characters) throwsLibraryLoadExceptionand the plugin does not load. - If none of the repositories can serve the jar, the plugin does not load.
- A
.pomthat fails to parse is not fatal: the jar is still usable, only its transitive dependencies are dropped (a warning is logged). - The XML parser is hardened against XXE (external entities, DTD, schema) — malicious POMs cannot reach the network or read local files through the parser.
For popular libraries on Maven Central, prefer libraries over shading. Keep repositories for private Nexus/Artifactory URLs — public coordinates should resolve without it.
Before You Continue
- The most common load failures are a missing
plugin.ymland a wrongmainclass name - When
librariesfail to resolve, the server log prints the offending coordinate and the repositories it tried — check the URL and your network before falling back to shading - If your structure and metadata look correct, continue to Run and Debug Your Plugin