Minecraft Server Async Processing: How to Use Multithreading to Eliminate Lag
# Minecraft Server Async Processing: How to Use Multithreading to Eliminate Lag
Minecraft's core game loop runs on a single main thread. Every tick — mob AI, redstone signals, player movement, chunk loading — competes for that one thread. Once it's overloaded, your TPS drops and players feel lag. But there's good news: modern server software and smart plugin configuration can push a surprising amount of work off the main thread entirely.
This guide explains how async processing works in Minecraft servers, which tasks can be offloaded, and exactly how to configure your setup for maximum throughput.
Why the Single-Threaded Model Is a Bottleneck
Vanilla Minecraft and most forks still rely heavily on a single-threaded tick loop. The server must complete every tick in under 50ms to maintain 20 TPS. If any individual task — say, a poorly written plugin or a complex chunk generation request — takes too long, the entire tick is delayed.
This is fundamentally different from modern web servers or databases that spawn worker threads freely. Minecraft's world state is deeply interconnected, making naive multithreading dangerous: two threads modifying the same chunk simultaneously can corrupt data or crash the server.
That said, many operations *can* be done safely off the main thread.
What Can Be Safely Offloaded Async
Not everything can run asynchronously, but these categories are generally safe:
- Disk I/O — Reading/writing chunk data, player data, and log files
- Network I/O — Sending packets to clients (Paper handles much of this already)
- Database queries — MySQL/SQLite reads and writes from plugins
- HTTP requests — Plugin API calls, Discord webhooks, analytics
- World pre-generation — Tools like Chunky run generation in background threads
- Chat processing — Anti-spam checks, format parsing
- Plugin-side calculations — Leaderboard updates, economy batch processing
Things that must stay on the main thread:
- Direct block/entity manipulation
- Inventory changes affecting live players
- Spawning entities
- Anything interacting with the world state
Paper's Built-In Async Improvements
Paper has shipped numerous async optimizations that you get for free by switching from vanilla Spigot:
Async chunk loading and saving is enabled by default in Paper. Chunks are loaded from disk on background threads and only handed to the main thread once ready. This dramatically reduces the stutter you see when players move quickly through unexplored terrain.
In paper-global.yml, you can tune the chunk system thread count:
chunk-system:
worker-threads: -1 # -1 = auto (recommended)
gen-parallelism: default
Leave worker-threads at -1 unless you have a specific reason to cap it. Paper will use approximately half your available CPU cores for chunk work, which is a sensible default for most servers.
Async pathfinding was introduced in newer Paper builds. Mob pathfinding calculations — historically one of the biggest main-thread hogs — are computed on separate threads with results applied back safely. Enable it in paper-world.yml:
mobs:
use-async-pathfinder: true
This alone can recover 10–20% main thread capacity on servers with heavy mob populations.
Pufferfish and Async Entity Activation
Pufferfish, a performance-focused Paper fork, introduces async entity tracking (DAB — Dynamic Activation of Brains). Instead of ticking every entity's AI every single tick, Pufferfish staggers and offloads brain calculations based on distance from players.
If you're running Pufferfish, check pufferfish.yml:
dab:
enabled: true
start-distance: 12 # Entities beyond this get reduced ticking
max-tick-freq: 20 # Minimum tick rate for distant entities
activation-dist-mod: 8 # Tune aggression of distance scaling
These settings significantly cut the per-tick cost of large entity farms and crowded areas without visibly impacting gameplay.
Folia: True Multithreading for Large Servers
[Folia](https://github.com/PaperMC/Folia) is PaperMC's experimental regionalized multithreading fork. Instead of one main thread, Folia splits the world into independent regions, each with its own thread. Regions that don't share borders can tick simultaneously.
This is a fundamentally different architecture:
Vanilla/Paper: [Main Thread] → all chunks, all entities, all players
Folia: [Thread 1] → Region A | [Thread 2] → Region B | [Thread 3] → Region C
Folia is best suited for:
- Large survival servers with players spread across the map
- Minigame networks where arenas are physically separated
- Servers with 8+ CPU cores to actually parallelize across
Important caveats: Folia requires plugins to be explicitly compatible. Most mainstream plugins are not yet Folia-ready, though adoption is growing rapidly. Check the plugin's GitHub or documentation before migrating.
Folia configuration is similar to Paper but adds region thread settings in config/paper-global.yml:
regionised:
threads-per-region: 1 # Usually 1; regions auto-scale by load
Making Your Plugins Async-Friendly
Even on standard Paper, your plugins can do async work. If you write or configure custom plugins, follow these rules:
Always use async schedulers for I/O:
// Bad — blocking main thread:
ResultSet rs = database.query("SELECT * FROM players");
// Good — async with callback:
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
ResultSet rs = database.query("SELECT * FROM players");
Bukkit.getScheduler().runTask(plugin, () -> applyResult(rs));
});
For server admins (not developers), look for these async-friendly plugin alternatives:
- LuckPerms — fully async permission lookups
- HikariCP-backed database plugins — connection pooling reduces blocking
- Chunky — async world pre-generation
- Plan — async player analytics
Avoid plugins that advertise features but have no async support for their database operations — these are common culprits for random main-thread spikes.
Monitoring Async vs Main Thread Usage
Knowing *which* thread is causing lag is critical. Use [Spark](https://spark.lucko.me/) to profile your server:
/spark profiler --thread *
The --thread * flag captures all threads, not just the main thread. This lets you see if chunk workers, database threads, or plugin async tasks are consuming excessive CPU — which can still indirectly hurt performance through resource contention.
PulseNode's real-time monitoring dashboard can surface these thread-level anomalies automatically, alerting you when background worker threads spike unexpectedly without requiring you to manually trigger profiler sessions.
Quick Async Checklist
- [ ] Running Paper or a Paper fork (not vanilla Spigot)
- [ ]
use-async-pathfinder: trueinpaper-world.yml - [ ] Chunk worker threads set to
-1(auto) inpaper-global.yml - [ ] Pufferfish DAB enabled if using Pufferfish
- [ ] Plugins use async schedulers for database/HTTP calls
- [ ] World pre-generated with Chunky to eliminate async chunk gen overhead
- [ ] Spark profiling with
--thread *to verify no thread starvation
Conclusion
The single-threaded limitation of Minecraft is real, but it's not insurmountable. By running Paper (or Pufferfish/Folia), enabling async pathfinding, using async-aware plugins, and understanding which tasks belong on which thread, you can extract dramatically more performance from the same hardware.
If you want continuous visibility into how your threads are behaving without manual profiling, [PulseNode](https://pulsenode.tech) monitors your server's performance in real time and flags thread-related bottlenecks before they become player-facing lag — so you can optimize proactively instead of reactively.