Architecture

Server & Plugins

๐Ÿ–ฅ๏ธ Server & Plugins#

Software Hierarchy#

Rank Software Description
๐Ÿ‘‘ Purpur Paper + extra config switches + permissions
๐Ÿฅˆ Paper Modern baseline, best docs/support
๐Ÿฅ‰ Spigot Legacy baseline, lower perf ceiling
๐Ÿ’€ Vanilla Just don’t

Rule: Start from Paper/Purpur, not Spigot, unless you have a specific compatibility constraint.

JVM Arguments (Aikar’s Flags)#

java -Xms10G -Xmx10G \
  -XX:+UseG1GC -XX:+ParallelRefProcEnabled \
  -XX:MaxGCPauseMillis=200 \
  -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40 \
  -XX:G1HeapRegionSize=8M \
  -XX:InitiatingHeapOccupancyPercent=15 \
  -XX:SurvivorRatio=32 -XX:MaxTenuringThreshold=1 \
  -XX:+PerfDisableSharedMem -XX:+AlwaysPreTouch \
  -jar purpur.jar nogui

Tips:

  • Always set -Xms = -Xmx (prevents resizing lag)
  • Leave 1-2GB RAM for the OS

Configuration Tweaks#

server.properties#

Setting Value Why
simulation-distance 4 Ticks entities only nearby โ€” CPU saver
view-distance 10 Visuals remain high
network-compression-threshold 256 Good bandwidth/CPU balance
enforce-secure-profile false Required for offline-mode bots

bukkit.yml#

Setting Value Why
spawn-limits.monsters 50 Default 70 is overkill per player
ticks-per.monster-spawns 4 Less frequent spawns
chunk-gc.period-in-ticks 400 Unloads empty chunks faster

spigot.yml#

Setting Value Why
entity-activation-range An:16, Mo:24 Mobs stop ticking sooner โ€” huge gain
entity-tracking-range Pl:48, An:48 Lowers network packet spam
merge-radius.item 4.0 Stacks items aggressively
save-user-cache-on-stop-only true Reduces disk I/O

paper-global.yml#

Setting Value Why
max-entity-collisions 2 Prevents cramming lag
grass-spread-tick-rate 4 Slower grass spread
container-update-tick-rate 3 Less frequent inventory checks
optimize-explosions true Efficient TNT math
prevent-moving-into-unloaded-chunks true Prevents glitchy lag spikes

Purpur โ€” Extra Config#

Purpur adds purpur.yml on top of all Paper config:

Setting Purpose
use-alternate-keepalive Reduce false timeout kicks
tps-catchup Catch-up behavior after lag
startup-commands Console commands on startup
lagging-threshold Threshold for lag-aware behavior
register-minecraft-debug-commands Expose hidden commands (admin-only)

Useful built-in commands: /purpur reload ยท /purpur version ยท /ping ยท /uptime ยท /tpsbar ยท /compass

Purpur Installation#

Requires Java 21+:

# Latest build for a version
curl -fsSL "https://api.purpurmc.org/v2/purpur/1.21.5/latest/download" -o purpur.jar
java -Xms4G -Xmx4G -jar purpur.jar --nogui

Spark Profiling#

Paper 1.21+ bundles Spark:

/spark profiler start --timeout 600

Inspect hot paths (plugins, entities, tasks). Tune root cause, not random knobs.

World Pregeneration (Chunky)#

#1 Lag Killer โ€” generate chunks before players explore:

chunky radius 5000
chunky start
chunky progress    # check status
chunky pause       # pause if needed
chunky continue

Nether: chunky world world_nether && chunky start

โŒ Common Anti-Patterns#

  • Enabling many gameplay toggles at once
  • Treating config packs as universal truths
  • Skipping pregen and blaming plugins for chunk lag
  • Giving broad wildcard perms to regular players
  • Running updates without rollback artifacts

โœ… Production Checklist#

  • Java 21+, startup script with sane memory headroom
  • Configs tracked in git
  • Spark baseline report captured
  • Chunky pregen done for active worlds
  • Purpur feature toggles documented by intent
  • Permission model audited
  • Rollback JAR + config snapshot ready

Dynmap Optimization#

Map Plugin Comparison#

Dynmap ๐Ÿฆ– BlueMap ๐ŸงŠ Pl3xMap โšก
View 2D + 3D Iso Full 3D WebGL 2D Top-down
Performance Heavy Medium (async) Ultra-light
Storage Massive Large Tiny

Dynmap Tuning#

# Quality vs speed (vlowres = 16x faster than hires)
deftemplatesuffix: vlowres

# Image format (JPG > PNG for storage)
image-format: jpg
image-quality: 80

# Disable lag factories
render-triggers:
  - chunkgenerated
  # - playermove    โ† DISABLE
  # - blockplaced   โ† DISABLE
  - join
  - quit

# Throttle
renderinterval: 1.0
tiles-rendered-at-once: 1

External Webserver (Critical)#

NEVER use the internal webserver for public servers:

disable-webserver: true
json-file: true
webpath: /var/www/html/dynmap

Serve via Nginx with tile caching (expires 7d).

Dynmap Commands#

  • /dynmap pause all โ€” before heavy server activity
  • /dynmap purgequeue โ€” clear backed-up queue
  • /dynmap fullrender world โ€” run overnight in screen

Plugin Architecture Best Practices#

Project Layout#

src/main/java/com/example/myplugin/
  MyPlugin.java
  bootstrap/
  command/
  listener/
  service/
  domain/
  infra/
  config/

Gradle (Kotlin DSL) Baseline#

repositories {
  maven("https://repo.papermc.io/repository/maven-public/")
}
dependencies {
  compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT")
  testImplementation("org.junit.jupiter:junit-jupiter:5.11.4")
  testImplementation("com.github.seeseemelk:MockBukkit-v1.21:4.63.0")
}
java {
  toolchain.languageVersion.set(JavaLanguageVersion.of(21))
}

Lifecycle Discipline#

  • onLoad: Minimal setup only, avoid Bukkit API
  • onEnable: Register listeners/commands, init services, validate config
  • onDisable: Cancel tasks, flush state, close DB/HTTP resources
  • Never rely on /reload โ€” full restart is the only supported path

Threading Rules#

  • World/entity/block mutations: main thread (Paper) or correct context (Folia)
  • DB/HTTP/files: async
  • Never: Thread.sleep() on main thread, blocking I/O on sync tasks
  • Always: async-to-sync pattern for DB โ†’ UI updates

Folia Support#

# paper-plugin.yml
folia-supported: true

Only enable after real validation. Use entity scheduler for entity-bound work, region scheduler for location-bound work.

PDC (PersistentDataContainer)#

NamespacedKey key = new NamespacedKey(plugin, "custom-tier");
item.editPersistentDataContainer(pdc -> pdc.set(key, PersistentDataType.INTEGER, 3));
  • Reuse NamespacedKey objects
  • Use getOrDefault for null-safe reads
  • PDC is NOT auto-copied between holders

Security Checklist#

  • Validate command args strictly
  • Cooldowns for heavy commands
  • Per-player/IP rate limits
  • HTTP timeouts + retries with jitter
  • Secrets in env vars, never hardcoded