"Sir, the JVM has booted and Spigot is in your local Maven cache. I trust your
onEnableis more compelling than a/hellocommand." — J.A.R.V.I.S. (probably)
Why this guide exists
This guide has quite a bit of personal value for me. I started my coding adventure right here; with Minecraft plugin development. Back during Covid (hard to believe that's already four years ago) I had way too much free time and a tendency to be bored. At some point my friends and I started our own Minecraft server, and from there on the rest was history. I taught myself Java and everything around plugin development via YouTube tutorials, StackOverflow, reading through tons of forum posts, and just trying things out. Personally, I would've loved someone who actually explained the concepts, walked through things step by step, and dug into the deeper reasons — without dropping the word "trivial" into every other sentence (I am glad StackOverflow is dying). That's why I chose to add this category to my guides, eventhough they might not really fit most of the "technical" categories featured.
Minecraft plugin development sits in an awkward spot. The server APIs you actually want to compile against — Spigot and Paper — have... opinions about how you obtain them. Spigot can't be redistributed because it patches Mojang code, so you can't just declare a Maven dependency and let the resolver do its thing — you have to run BuildTools locally first, which produces a Spigot artifact straight into your machine's ~/.m2 repository. Paper sidesteps that with a public Maven repo, but most tutorials online still walk you through the Spigot dance by default. Mix in five different "minimum JDK" requirements depending on which Minecraft version you target, a plugin.yml file the runtime is unforgiving about, and an IDE wizard that doesn't know any of this — and you've spent half an evening on setup before you've written a single line of game code.
This guide walks the seven pieces:
- Picking and installing the right JDK for your target Minecraft version.
- Running BuildTools to put a Spigot artifact into your local Maven repository.
- Bootstrapping a Maven project (IntelliJ wizard or plain
mvn archetype:generate). - Writing the
pom.xml— Spigot and Paper side-by-side, pick whichever you're targeting. - Filling in a bare-minimum
plugin.ymland aJavaPluginsubclass with a single/hellocommand. - Running
mvn packageand dropping the resulting.jarinto a local server'splugins/folder. - Day-to-day workflow notes for iterating fast (rebuild → reload).
Scope: this guide gets you from nothing to a working
/helloplugin on either Spigot or Paper. It does not cover Mojang mappings, NMS access (net.minecraft.server.*), Paper'spaperweight-userdevtoolchain, or cross-version plugin packaging — those are the next milestone, and a different guide.
IDE-wise: the click-paths and screenshots are IntelliJ IDEA Community / Ultimate, but everything in here works the same on VS Code (with the Java Extension Pack) or Eclipse — Maven does the heavy lifting; the IDE is just an editor with a green ▶ button.
Prerequisites
- A working terminal (PowerShell / Windows Terminal / WSL / macOS Terminal — anything that can run
java -versionandmvn -v). - Git installed and on PATH — BuildTools clones the Spigot/CraftBukkit/Bukkit source repos under the hood.
- A local Minecraft server matching the version you want to develop against — vanilla, Spigot, or Paper, whichever flavour your plugin is targeting. We'll need somewhere to drop the built jar in Step 6.
That's it. Maven and the JDK we'll install in the next step.
Step 1 — Install the right JDK (and Maven)
Minecraft's minimum JDK has crept upward over the years; pick the version that matches what you're targeting. Newer is fine — a JDK 21 install will compile for older targets via <source> / <target> in the pom.xml.
| Minecraft version | Minimum JDK | Recommended JDK |
|---|---|---|
| 1.16.x | 8 | 11 |
| 1.17.x – 1.20.4 | 17 | 17 |
| 1.20.5 – 1.21.x | 21 | 21 |
This guide uses JDK 21 so the same steps work for any current MC version. If you're targeting older MC, swap the version numbers but everything else holds.
1a) Install via SDKMAN (recommended)
If you live in a terminal, SDKMAN is the painless route:
curl -s "https://get.sdkman.io" | bash
sdk install java 21.0.4-tem
sdk install maven
tem is the Eclipse Temurin distribution — the de-facto community OpenJDK build. Confirm:
java -version
mvn -v
You should see openjdk version "21.x.x" from one and Apache Maven 3.9.x from the other, with JAVA_HOME pointing at the JDK SDKMAN just installed.
1b) Install via IntelliJ (no terminal needed)
File → Project Structure → SDKs → +, pick a JDK version, accept the defaults — IntelliJ downloads and registers it under your user profile. Maven is bundled with IntelliJ, so for IDE-only workflows you're done. Trade-off: you won't have java / javac / mvn on your PATH, so terminal-based commands later in the guide won't work without manually setting JAVA_HOME and PATH. Use SDKMAN if you want both.
1c) Install manually (the boring way)
Download a Temurin JDK installer from adoptium.net, run it, and ensure JAVA_HOME is set:
- Windows (PowerShell, persistent):
setx JAVA_HOME "C:\Program Files\Eclipse Adoptium\jdk-21.0.4"(use your real install path). - macOS:
export JAVA_HOME=$(/usr/libexec/java_home -v 21)in your~/.zshrc. - Linux:
export JAVA_HOME=/usr/lib/jvm/java-21-openjdkin your~/.bashrc.
For Maven: download from maven.apache.org, unzip, add bin/ to PATH.
Why JDK and not JRE? The JRE only runs Java code; the JDK includes the compiler (
javac) and the tools Maven invokes. Modern JDK distributions don't ship a separate JRE anymore.
Step 2 — Build Spigot locally with BuildTools
Skip this step if you're targeting Paper only — Paper publishes its API to a public Maven repo, no local build required.
If you want Spigot in your pom.xml, you have to put it there yourself. Spigot's licensing forbids redistributing pre-built jars because they contain patched Mojang code, so the Spigot team ships a tool — BuildTools — that downloads, patches, and installs Spigot into your local Maven repository (~/.m2/repository/).
2a) Set up a workspace
Pick a folder you don't mind being a few hundred megabytes:
mkdir -p ~/spigot-build
cd ~/spigot-build
2b) Download BuildTools.jar
curl -O https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar
(Or grab it via your browser from https://hub.spigotmc.org/jenkins/job/BuildTools/.)
2c) Run BuildTools
java -jar BuildTools.jar --rev 1.21.4
Replace 1.21.4 with the Minecraft version you're targeting. Omit --rev to get the latest stable.
What happens, in order:
- BuildTools clones the CraftBukkit, Bukkit, Spigot, and Spigot-API source repos.
- Decompiles the official Mojang jar for the requested version.
- Applies the Spigot patches.
- Compiles the resulting code.
- Runs
mvn installfor each artifact, putting them in~/.m2/repository/org/spigotmc/spigot-api/.
This takes anywhere from 3 to 15 minutes depending on your machine and connection. Coffee break.
2d) Verify
After BuildTools finishes, check that the Spigot artifact landed:
ls ~/.m2/repository/org/spigotmc/spigot-api/
You should see a folder named after the version you built (e.g., 1.21.4-R0.1-SNAPSHOT). If it's there, you're done with BuildTools — you don't need to re-run it unless you want to target a different MC version.
Common BuildTools failures: Git not on PATH (install Git), running BuildTools on JDK 8/11 when targeting modern MC (BuildTools wants the same JDK as the target version requires), or filesystem case-sensitivity on Windows when the build folder is in OneDrive (move it elsewhere). See Known issues at the end.
Step 3 — Create the Maven project
Two routes — pick one. Result is the same skeleton.
3a) IntelliJ New Project wizard
File → New → Project → Maven Archetype. The bits to set:
- Name:
HelloPlugin(or whatever). - GroupId:
com.yourname.helloplugin(reverse-DNS, lowercase). - ArtifactId:
helloplugin(same as the project folder name is conventional). - JDK: the one you installed in Step 1.
- Archetype:
org.apache.maven.archetypes:maven-archetype-quickstart(the boring default).
Click Create. IntelliJ generates a project with a src/main/java skeleton and a starter pom.xml. We'll rewrite the pom.xml in Step 4.
3b) Plain mvn archetype:generate
Same result, no IDE:
mvn archetype:generate \
-DgroupId=com.yourname.helloplugin \
-DartifactId=helloplugin \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.4 \
-DinteractiveMode=false
cd helloplugin
Open the resulting folder in any editor — it's a regular Maven project from here on.
Step 4 — Configure pom.xml for Spigot or Paper
Open the auto-generated pom.xml and replace its body with one of the two below. The shape is identical — only the repository and dependency coordinates change.
4a) For Spigot
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yourname.helloplugin</groupId>
<artifactId>helloplugin</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>spigotmc-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.21.4-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
4b) For Paper
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yourname.helloplugin</groupId>
<artifactId>helloplugin</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>papermc-repo</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21.4-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
▶What's actually different between the two? (click to expand)
| Block | Spigot | Paper |
|---|---|---|
| Repository URL | hub.spigotmc.org/nexus/... (you populated this with BuildTools in Step 2) | repo.papermc.io/repository/maven-public/ (public, no BuildTools) |
groupId | org.spigotmc | io.papermc.paper |
artifactId | spigot-api | paper-api |
| Version format | <MC>-R0.1-SNAPSHOT | Same |
The Paper API is a superset of the Spigot API — code that compiles against spigot-api will compile and run against Paper unchanged. Going the other way (Paper-only APIs on Spigot) won't.
<scope>provided</scope> is critical: it tells Maven the API will be supplied by the server at runtime, so don't bundle it into your jar. Forget provided and your plugin jar balloons by ~10 MB and the server complains about duplicate classes on load.
After saving pom.xml, refresh Maven so the new dependency resolves:
- IntelliJ: the bottom-right notification offers "Load Maven changes" — click it. Or
Maventool window → ↻ icon. - CLI:
mvn dependency:resolve.
Step 5 — Write the plugin
Two files: a plugin.yml resource (the server reads this on startup to know how to load you) and a JavaPlugin subclass.
5a) src/main/resources/plugin.yml
Create the resources folder if it doesn't exist (src/main/resources/), then drop in:
name: HelloPlugin
version: 1.0
main: com.yourname.helloplugin.HelloPlugin
api-version: '1.21'
commands:
hello:
description: Greets the sender.
usage: /hello
The main field has to match your subclass's fully qualified name exactly, including capitalisation. The single most common "my plugin doesn't load" error is a typo here.
5b) src/main/java/com/yourname/helloplugin/HelloPlugin.java
package com.yourname.helloplugin;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.java.JavaPlugin;
public final class HelloPlugin extends JavaPlugin {
@Override
public void onEnable() {
getLogger().info("HelloPlugin enabled.");
}
@Override
public void onDisable() {
getLogger().info("HelloPlugin disabled.");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (command.getName().equalsIgnoreCase("hello")) {
sender.sendMessage("Hello, " + sender.getName() + "!");
return true;
}
return false;
}
}
That's a complete plugin. onEnable runs when the server loads it, onDisable when the server stops or /reloads, and onCommand is the dispatcher for everything you registered in plugin.yml.
Bukkit / Spigot / Paper API note:
JavaPlugin,Command,CommandSendercome fromorg.bukkit.*— the original Bukkit API. Spigot and Paper both extend it; for a/hellocommand you'll never touch anything Spigot- or Paper-specific.
Step 6 — Build and load
6a) Build the jar
mvn clean package
The output lands in target/helloplugin-1.0-SNAPSHOT.jar. That's the file you ship.
6b) Drop it into a server
Stop your local server (or don't — /reload works for the brave). Copy the jar into the server's plugins/ folder:
cp target/helloplugin-1.0-SNAPSHOT.jar /path/to/server/plugins/
Start the server. You should see HelloPlugin enabled. in the console somewhere among the startup logs.
6c) Test
Connect to your server and run /hello in chat. You should see Hello, <yourname>! echoed back.
If /hello shows as an unknown command, the plugin didn't load — check the server console for a stack trace. Usually it's the main: field in plugin.yml not matching your class name.
Day-to-day notes
providedscope is mandatory. If you forget it, your jar will be huge and the server may refuse to load it because of duplicate classes. Sanity checktarget/<name>.jarsize — a/helloplugin should be ~5–10 KB, not 10 MB.- Iterating fast:
mvn package→ copy jar →/reload confirmon the server. Pre-1.20 you could/reloadfreely; on modern versions it warns you first because some plugins hate being reloaded. For correctness, restart the server. For speed, ignore the warning. plugin.ymltypos account for ~80% of "my plugin doesn't load" issues. Wrongmain:class, missingapi-version:, YAML indentation broken (tabs vs. spaces).- Spigot vs Paper choice: writing against
spigot-apiis the lowest common denominator — your plugin runs on both. Writing againstpaper-apigives you more APIs (Adventure components, async chunk APIs, scheduler improvements, etc.) but locks you to Paper. Pick based on who you want to ship to. api-versioninplugin.yml(e.g.,api-version: '1.21') tells the server which compatibility mode to use. Set it to your target MC version. Older plugins without it run in legacy compatibility mode and miss out on bytecode optimisations.- Don't commit
~/.m2to git — that's your machine-wide Maven cache. The Spigot artifact only exists there because BuildTools put it there.
Known issues & caveats
- BuildTools fails with "git not found" — install Git and ensure
git --versionworks in your terminal. Windows installer adds it to PATH; macOS/Linux:brew install git/sudo apt install git. - BuildTools fails with
UnsupportedClassVersionError— you're running BuildTools on an older JDK than the target MC version requires. For 1.21+, run BuildTools with JDK 21. - BuildTools succeeds but the artifact is missing from
~/.m2— usually a permission issue with the Maven local repo.chmod -R u+w ~/.m2/repository(Linux/macOS) or check Windows user permissions on the directory. - Build hangs on Windows + OneDrive — OneDrive's filesystem layer confuses BuildTools. Move the build workspace out of any OneDrive-synced folder.
Plugin failed to load: NoClassDefFoundError— yourplugin.yml'smain:references a class that doesn't exist (typo, wrong package, or you forgot to actually put the class in the right folder). Open the built jar (it's a zip) and check the contents.api-versionwarnings in console — newer servers complain if your plugin omitsapi-version. Add it toplugin.ymland they go away.Could not resolve org.spigotmc:spigot-api— BuildTools didn't run successfully, or it ran for a different version than you've pinned inpom.xml. Re-run BuildTools with--rev <version>.- Paper changed something between minor versions and now my plugin breaks — Paper occasionally tightens deprecations between MC versions. Check the Paper migration guide for your target version.
Useful links
Spigot / Paper / Bukkit
- Spigot BuildTools wiki — official docs, including the
--rev,--remapped, and other flags. - Spigot plugin tutorial — official getting-started, written for Maven.
- PaperMC documentation — Paper's docs site, plus migration guides between MC versions.
- Bukkit/Spigot/Paper Javadocs — searchable API docs for everything in
org.bukkit.*.
Maven
- Apache Maven docs — official guides; the dependency-scope page is a useful reference.
maven-shade-plugin— for when you eventually need to bundle a third-party library inside your plugin jar without classpath collisions.
Java
- Eclipse Temurin (community OpenJDK builds) — the JDK distribution we install above.
- SDKMAN — install/switch between multiple JDKs and tooling versions on the same machine.