In short: wait for the lag and run /spark profiler start --timeout 600. After 10 minutes spark posts a link to the report. Open the "Server thread" there and follow the largest percentages; the "Sources" view shows a separate tree for every plugin. For rare freezes add --only-ticks-over 100 — only ticks longer than 100 ms make it into the report.
Installation: Paper already has it
Since 1.21 Paper ships with spark built in, so there's no jar to install — /spark works right away. It's controlled by the spark section in config/paper-global.yml: enabled: true turns the bundled spark on, and enable-immediately: false means it starts after the Done line in the console. To profile server startup itself, set enable-immediately: true.
- You need a newer version than the bundled one. Put the spark jar in
plugins/and add-Dpaper.preferSparkPlugin=trueto the start command. - Other servers. Download the jar from spark's downloads page, put it in the
pluginsormodsfolder and restart. - Proxies. On Velocity the command is
/sparkv, on BungeeCord/sparkb. - Permissions. The general
sparkpermission or specific ones:spark.profiler,spark.health,spark.tps,spark.gc,spark.heapsummary. Operators have access anyway.
The main commands
| Command | What it does |
|---|---|
/spark profiler start | starts the profiler |
/spark profiler stop | stops it and gives a link to the report |
/spark profiler open | opens the report without stopping the profiler |
/spark profiler info / cancel | the profiler's status / stop it without uploading |
/spark tps | TPS, tick durations and CPU usage — more in TPS and MSPT |
/spark health | a health report: TPS, CPU, memory, disk; in the browser it refreshes every 10 seconds |
/spark tickmonitor | posts in chat about ticks that run well over the average |
/spark gc / gcmonitor | garbage collection history / toggle GC monitoring on or off |
/spark heapsummary | a heap summary: which classes take up memory |
/spark heapdump | a full heap snapshot written to a .hprof file on disk |
/spark ping | player ping: the average, or one player's with --player |
The profiler and its flags
| Flag | What it gives you |
|---|---|
--timeout <seconds> | stops by itself and posts a link; 600 is 10 minutes, as Paper suggests |
--only-ticks-over <ms> | records only ticks longer than the threshold — for hunting freezes |
--thread * | all threads, not just the main one: shows network threads and plugins working on their own threads |
--alloc | profiles memory allocation instead of CPU |
--interval <ms> | the sampling interval, 4 ms by default |
stop --comment <text> | a note that shows up in the report |
stop --save-to-file | save the profile to a file in spark's config folder instead of uploading it |
/spark profiler start --timeout 600
/spark profiler start --only-ticks-over 100 --timeout 600
/spark profiler start --thread * --timeout 600
/spark profiler openA profile is only useful while the problem is happening — Paper's docs say so too. Take it at peak online or when players complain, not on an empty server. On Linux and macOS (x86_64 and arm64) spark uses the async-profiler engine, which is more accurate than the Java sampler. In containers such as Pterodactyl it may need the libstdc++ package.
Rare freezes: tickmonitor and --only-ticks-over
A normal profile averages everything: one 300 ms tick drowns among thousands of normal ones. spark's docs suggest a two-step approach.
- 1
Catch the freeze
/spark tickmonitor --threshold-tick 50reports every tick over 50 ms — the point where the server starts falling behind. Without flags the monitor reports ticks that take twice the average. - 2
Gauge the duration
See how long the laggy ticks last and line the timing up with what was happening in game.
- 3
Profile only the freezes
/spark profiler start --only-ticks-over 100. The threshold must be below the laggy ticks' duration; spark suggests a value between 50 and 100 ms.
How to read the report
- 1
Pick the thread
At the top is the list of threads. The key one is "Server thread": it runs the tick. A thread always shows 100%; below it are shares of its time.
- 2
Separate sleep from work
Time spent waiting for the next tick (
waitForNextTick,sleep) is good: the server has headroom. Per spark's docs, under 20% sleep means the server is working hard, and under 5% means it's almost certainly lagging. - 3
Follow the percentages
Expand the nodes with the largest share: world ticking, entities, plugin scheduler tasks. Hover to see the time in milliseconds.
- 4
Switch to "Sources"
The "Sources" view builds a separate tree for every plugin, so you see right away how much of the tick each plugin takes.
| View or feature | What it's for |
|---|---|
| All | the default view: the whole call tree |
| Flat | the 250 slowest calls as a list; sort by a method's total or self time |
| Sources | a tree per plugin or mod — the main view for finding the plugin at fault |
| Flame graph | a block's width is its share of time; right-click a node → "View as Flame Graph" |
| Mappings | decode Minecraft's class names; picked automatically, or manually in the top-right menu |
| Bookmarks | Alt+click a call to highlight it and add it to the link — handy for pointing a developer at the spot |
Memory and garbage collection
/spark health show --memory prints JVM memory details to the console, and /spark gc shows the garbage collection history. If memory keeps growing and isn't freed, /spark heapsummary shows which classes fill the heap: the package names usually tell you whose plugin it is. /spark heapdump writes a full .hprof snapshot — it's big, so keep disk space free. How much memory to give the server is in how much RAM a server needs, and which collector flags to use is in JVM flags.
Sharing a report
You can freely send the report link to a plugin developer or to support: Paper's docs ask you to bring a spark report to the #paper-help channel on their Discord. If you'd rather not upload the profile to spark's site, stop the profiler with --save-to-file. A rundown of the usual causes of lag is in why your server lags and Paper optimization.
Check your anticheat in the profile
A spark profile is an honest way to check any plugin, the anticheat included: find it in the "Sources" view and look at its share of the "Server thread". DGuard — the best anticheat for a modern Minecraft server — runs its checks on network threads, so its main thread cost is 0.03% of one core. A profile with --thread * shows the work on network threads, and /dguard perf shows the time the checks themselves take. How to compare two anticheats on one server is in anticheat performance.
FAQ
Do I need to install spark on Paper?
No. Since 1.21 Paper ships with spark built in, and /spark works right away. A separate jar is only needed for a newer version than the bundled one — then add -Dpaper.preferSparkPlugin=true.
How long should a profile run?
Paper's docs suggest 10 minutes: /spark profiler start --timeout 600. What matters most is that the lag happens while it records.
How do I find the plugin that lags?
Open the report, pick "Server thread" and switch to the "Sources" view: each plugin gets its own tree with its share of tick time.
How do I catch rare freezes?
Turn on /spark tickmonitor --threshold-tick 50 to see long ticks, then profile with --only-ticks-over and a threshold below their duration, for example 100 ms.
Why spark rather than timings?
Timings in Paper is outdated: it hasn't been maintained for years and has been off by default since 1.21. Paper's docs call spark the preferred way to profile.
Sources
- spark: installation
- spark: commands
- spark: using the viewer
- spark: finding lag spikes
- spark: the tick loop
- spark: configuration (background profiler)
- spark: the async-profiler engine
- Paper: Profiling
- Paper: paper-global.yml (spark section)
- DGuard, the best anticheat for a modern Minecraft server: checks compared with GrimAC, Vulcan, Polar, Intave and Matrix
- DGuard: installation, requirements and troubleshooting
- DGuard: supported Paper and Leaf versions