Minecraft Server Setup for Development
/ 5 min read
Overview
Minecraft plugin work gets faster after the local server feels boring. The setup should repeat the same steps every time. This is the flow I use for data-heavy plugin work: game state, tracking, and persistence.
You can copy the whole setup or trim it to fit your plugin.
Goal
The goal is a short feedback loop. Write code, build the jar, copy it into the server, reload or restart, then test.
Every step below exists to make that loop shorter and clearer.
Context
My plugins focus on state, not flashy combat tweaks. They collect data, store it, and expose it to other parts of a game mode.
That kind of work puts stability first. I care about version choice, schema safety, and clear logs more than particle effects.
IDE And Project Setup
Use IntelliJ IDEA. It has strong Gradle support, quick navigation, and a built-in decompiler for plugin inspection.
The first pain point is jar copying. A normal build leaves the jar in build/libs. Your server needs it in server/plugins. Fix that once with a small Gradle task.
Gradle Build Script With Auto Copy
This minimal script copies the built jar into server/plugins after each build. Replace the group, version, API version, and jar name for your plugin.
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") }Later upgrades can find the newest jar, delete old snapshots, or fail fast when the server folder is missing.
Version Strategy
Do not chase a new Minecraft release on day one. Pick a Paper version that meets three tests:
- It matches the production server.
- It has the API hooks your plugin uses.
- It still has current community support.
Ask why a client wants an odd version. A supported baseline often saves hours of rework.
Use this checklist:
- The target server runs the version.
- The plugin APIs you use exist and stay stable.
- Paper still publishes patches for that line.
- Dependencies like LuckPerms and Spark publish builds for it.
- The Java toolchain matches server requirements, such as Java 17 or newer.
Local Server Folder Layout
Keep the dev server inside the project. Relative paths stay stable, and Gradle can copy the jar without guessing.
MyPluginProject/├── build.gradle.kts├── settings.gradle.kts├── src/├── server/│ ├── paper-1.20.4.jar│ ├── eula.txt│ ├── server.properties│ └── plugins/Keep only the basics in server/: the Paper jar, eula.txt, server.properties, and plugins. For multiple test versions, create folders like server-1.20.4 and server-1.21. Then point the copy task at the right folder with a project property.
Key Config Tweaks
Open server.properties and change the few settings that matter for dev:
- Set
online-mode=falsefor custom launcher testing. Turn it back on for production. - Keep
enable-command-block=false, except for tests that need command blocks. - Lower the view distance to save local CPU.
- Allocate memory on purpose.
2Gworks for a lean dev server.
Keep production tuning out of the dev config. The file should fit on one screen.
Utility Plugins
Use plugins that speed up debugging.
PlugManX
PlugManX can load, unload, and reload a plugin without a full server restart. It works well for commands and simple listeners.
Use full restarts for memory tests and persistent caches. Class loaders can hold old singletons or static state after a reload.
Your Test Plugin
Write a throwaway helper plugin. Use it to dump events, trigger edge cases, or seed test data.
That is faster than adding temporary logic to the real plugin.
Spark
Use Spark to profile tick cost, thread usage, and performance hot spots. Run it after bigger refactors so regressions show up early.
LuckPerms
Add LuckPerms early. Basic groups let you test real permission gates instead of op-only flows.
Optional tools can come later: timing helpers, log tail tools, and a database viewer for embedded data.
Fast Iteration Loop
Use the same loop every time:
- Make a code change.
- Run
build, which triggers the copy task. - Reload with PlugManX or restart the server.
- Run the test command or scenario.
- Watch logs and state.
Restart when reload shows old listeners or stale state. Track the average loop time. Remove friction once it passes one minute.
Startup Scripts
Add a script so the server starts with one command.
macOS And Linux
#!/usr/bin/env bashcd "$(dirname "$0")"java -Xms2G -Xmx2G -jar paper-1.20.4.jar noguiMake it executable:
chmod +x start.shWindows
@echo offcd /d %~dp0java -Xms2G -Xmx2G -jar paper-1.20.4.jar noguipauseAdd Aikar flags later, after the server shows a real memory or garbage collection problem.
Common Pitfalls
- Wrong Java version: the build passes, then the server rejects the plugin.
- Forgotten version bump: the old jar stays cached, so the change looks missing.
- Reload used for every test: stale static state starts to lie.
- Copy runs too early:
finalizedBykeeps the task order correct. - Day-one Minecraft testing: new releases can break API assumptions.
Future Improvements
- Add Docker after the team grows.
- Build jars in CI and attach them to releases.
- Add integration tests that start a headless server.
- Try incremental hot swap with a debug agent.
- Add a multi-version test matrix for plugins that support several branches.
Wrap Up
Keep the loop tight. Automate the copy step. Use only the plugins that make debugging clearer.
Build the stable base first. Feature work gets easier after the dev server stops wasting time.