Minecraft Server Setup for Development
/ 5 min read
Overview
You build Minecraft plugins faster when your local server setup is boring, repeatable, and automated. This is the workflow I use for data heavy plugin work (game state, tracking, persistence). You can copy it and adapt it.
Goal
Short feedback loop: write code, build, drop plugin in server, reload or restart, test. Anything that slows that loop goes. Everything here exists to tighten it.
Context
My plugins focus on state not flashy combat tweaks. I collect, store, expose data other parts of a game mode need. That means I care about version stability, schema safety, and clear logs more than particle spam.
IDE and Project Setup
Use IntelliJ IDEA. Solid Gradle support. Fast navigation. Built in decompiler helps when you inspect other plugins. The pain point you hit early: copying your built jar into the server plugins folder every change. Fix that once with a tiny Gradle task.
Gradle Build Script With Auto Copy
Below is a minimal script. Replace names to fit your plugin. The copy task runs after build and moves the jar straight into server/plugins
.
plugins { java }
group = "me.yourname"version = "1.0"
repositories { mavenCentral() maven("https://papermc.io/repo/repository/maven-public/")}
dependencies { compileOnly("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT")}
java { toolchain.languageVersion.set(JavaLanguageVersion.of(17))}
tasks.register<Copy>("copyToServer") { val jarName = "MyPlugin-${project.version}.jar" from(buildDir.resolve("libs/$jarName")) into(layout.projectDirectory.dir("server/plugins")) doLast { println("Copied $jarName to server/plugins") }}
tasks.build { finalizedBy("copyToServer") }
Improvements you can add later: compute latest jar dynamically, delete old snapshots, fail if server folder missing.
Version Strategy
Do not chase the newest release the day it drops. Pick a Paper version that:
- matches the prod server,
- has the API hooks you need
- has current community support.
If a client is asking for an odd version ask why. Moving them to a well supported baseline saves you rework.
Checklist when picking a version:
- Server you target actually runs it
- Plugin APIs you depend on exist and are stable
- Paper build still gets patches
- Dependencies (LuckPerms, Spark) publish builds for it
- Java toolchain version matches requirements (17+ for recent Paper)
Local Server Folder Layout
Keep the dev server inside the project so relative paths stay stable and tasks can copy output without guessing.
MyPluginProject/├── build.gradle.kts├── settings.gradle.kts├── src/├── server/│ ├── paper-1.20.4.jar│ ├── eula.txt│ ├── server.properties│ └── plugins/
Inside server/
keep only what you need: jar, eula, properties, plugins. If you want multiple test versions create server-1.20.4
, server-1.21
etc and point the copy task with a project property.
Key Config Tweaks
Open server.properties
and adjust only what matters for dev:
online-mode=false
if you test with custom launchers (remember to re enable for prod)enable-command-block=false
(unless needed) reduces noise- Set a lower view distance to save local CPU
- Allocate memory with intent (2G is fine for a lean dev server)
Avoid cluttering config with production tuning flags. Dev config should be readable in one screen.
Utility Plugins
Use only what accelerates debug.
PlugManX
Lets you load unload reload a plugin without full restart. Good early while iterating on commands or simple listeners. Be aware that class loaders can retain singletons or static state. If you debug memory or persistent caches prefer a full restart.
Your Test Plugin
Write a throwaway helper plugin that dumps events to console, triggers edge cases, or seeds test data. Faster than wiring temporary logic into the real plugin.
Spark
Profile performance hot spots, tick cost, thread usage. Run it after bigger refactors to confirm no regression.
LuckPerms
Baseline permission control. Add basic groups early so you test real permission gates not op-only flows.
Optional later: timing tools, log tail utilities, database viewer for embedded data.
Fast Iteration Loop
Workflow:
- Code change
build
(copy task fires)- Use PlugManX reload OR restart server
- Run test command or scenario
- Observe logs and state
If reload shows weird ghosts (old listeners firing) restart instead. Track average loop time. If it drifts past a minute remove friction.
Startup Scripts
Add a script so you stop typing the full java command. Keep it simple first.
macOS Linux
#!/usr/bin/env bashcd "$(dirname "$0")"java -Xms2G -Xmx2G -jar paper-1.20.4.jar nogui
Make executable:
chmod +x start.sh
Windows
@echo offcd /d %~dp0java -Xms2G -Xmx2G -jar paper-1.20.4.jar noguipause
You can later add flags (Aikar flags) but only after you actually need them.
Common Pitfalls
- Wrong Java version: build compiles but server rejects plugin. Match toolchain to server.
- Forgot to bump plugin version: jar cached; you think code did not change.
- Using reload for everything: leads to stale static state.
- Plugin copies before compile finishes: rely on Gradle finalizedBy to enforce order.
- Testing on latest Minecraft the day it drops: breaks API assumptions.
Future Improvements
- Add Docker for reproducible environment when team size grows
- Wire CI to build and attach jar to releases
- Add integration tests that spin a headless server instance
- Add incremental recompile hot swap with a debug agent
- Add multi version test matrix if you support more than one branch
Wrap Up
Keep the loop tight. Automate boring copy steps. Limit plugins to the ones that buy clarity. Avoid premature scaling work until actual pain appears. Build a stable base then layer features.
Ship a clean dev setup first. The plugin quality follows.