Why Minecraft Runs on One Thread, and What Folia Breaks

Every Minecraft hosting guide tells you to buy clock speed over core count. This is the architectural reason why, and the one server platform that breaks the rule by splitting the world into independently ticking regions.

Why Minecraft Runs on One Thread, and What Folia Breaks
Photo by amirali mirhashemian / Unsplash
Last updated: August 14, 2026. Covers Paper, Purpur, Pufferfish, and Folia 26.1.2. Folia requires Minecraft 1.20.4 or later. This article explains the architecture; it isn't a recommendation to switch, and PaperMC's own guidance is that Folia isn't the right fit for most servers.

A 64-core CPU at a modest clock speed runs a Minecraft server worse than a cheap chip with fewer, faster cores. Every hosting guide states this as received wisdom. Almost none explain why, and the answer is a piece of software architecture worth understanding on its own terms: Minecraft's core game logic has run on a single thread since the beginning, and one project, Folia, spent years dismantling the reasons that was necessary. Here's the mechanism behind the CPU advice, what Paper and its forks optimize instead of parallelizing, and what Folia does differently at the code level.

The real reason: shared, mutable state

Minecraft's world is one large shared, mutable data structure. Every block, every entity, every redstone signal can affect any other block, entity, or signal nearby, and "nearby" changes from tick to tick as things move. Ticking that world on multiple threads at once means multiple threads reading and writing the same data at the same time. Without careful coordination, that produces race conditions: two threads modifying the same block or entity in the same instant, with the outcome depending on timing rather than game logic. That isn't a rare edge case specific to Minecraft. It's the default outcome of naive parallelism applied to any tightly interconnected simulation.

The single-thread model sidesteps the problem by construction: if only one thread ever touches world state, there's no race to have. Multiple projects tried to parallelize Minecraft over the years and hit this wall, producing unstable builds with corrupted world state and bugs that were hard to reproduce because they depended on thread timing. The single thread isn't an oversight. It's the load-bearing assumption almost everything else in the codebase, and every plugin ever written against it, depends on.

That's also the direct answer to the CPU question. Minecraft's tick loop, the code that processes one 1/20th-of-a-second slice of game time, runs on one core, in full, sequentially. A processor with a higher clock speed finishes that sequential work faster. A processor with more cores has more cores sitting idle while the one core doing the tick loop works alone.

What costs performance inside that one thread

Once everything funnels through one sequential loop, the specific things that cost performance stop being folklore and become simple arithmetic: more work per tick means a slower tick, means lower TPS (ticks per second, the standard measure of server health; 20 is full speed).

  • Redstone observers are the worst offender. They check for block updates constantly and fire immediately when triggered. A poorly designed observer chain can generate hundreds of updates per second, all landing on the same thread as everything else the server has to do that tick.
  • Hopper chains rank second. Every hopper transfer is work the single thread has to process, and long chains multiply that cost linearly.
  • Chunk state freezes, rather than pauses, on unload. When a chunk unloads, redstone circuits don't resume where they left off when it reloads. They resume based on whatever powered state they held at the moment of unload. That's why flying machines and precisely timed contraptions break when their chunk unloads and reloads: the timing relationship that made them work is gone, and only the last known state survives.
  • Spawn chunks are always loaded, a fixed area around world spawn that ticks permanently regardless of whether a player is nearby. That's why they're a common place to put mob farms and unattended automated builds, and why cramming too many active builds into spawn chunks creates a permanent tax on every tick, for every player, all the time.
We recommend reading Minecraft Bedrock Server RCE: CVE-2026-55010 to continue our Minecraft server-software coverage. Two looks at how the software runs under the hood, from opposite angles: security in that one, architecture in this one.

What Paper, Purpur, and Pufferfish optimize instead

This clears up a common misconception: Spigot, Paper, Purpur, and Pufferfish are all still single-threaded for world ticking. None of them break the core constraint above. What they do is make the work inside that single thread faster, and offload work that never needed to be on that thread to begin with.

Paper's contributions include async chunk generation and loading, moving the I/O-heavy work of reading chunks from disk or generating new terrain onto separate threads so the main tick thread isn't blocked waiting on a disk read. That's parallelism for support work, not for the tick loop itself; block updates, entity processing, and redstone logic still happen one at a time, on one thread. Paper's Starlight project rewrote the lighting engine for faster light recalculation, again speeding up work the main thread depends on rather than parallelizing the main thread itself. Purpur and Pufferfish build on the same foundation: more configuration options, smarter entity tracking, and reduced per-tick overhead, all incremental gains within the same single-threaded ceiling.

Platform World-tick threading What it parallelizes Typical hardware priority
Spigot / Paper Single thread Chunk I/O, terrain generation, lighting (Starlight) Fast single-core clock speed
Purpur Single thread Same as Paper, plus configurable gameplay tuning Fast single-core clock speed
Pufferfish Single thread Same as Paper, plus reduced entity/AI overhead Fast single-core clock speed
Folia Multiple threads, one per active region The tick loop itself, across a configurable thread pool Core count (16+ physical cores recommended)

None of that changes the shape of the problem: one thread, one world, sequential processing. That's what Folia breaks.

Folia: the architectural break

Folia is a fork of Paper built by Spottedleaf, a developer already known in the Minecraft optimization space. It started in August 2020 and was built on prerequisite Paper work, including the Starlight lighting rewrite and a chunk-system rewrite, that had to exist before regionized ticking was possible.

The core idea: Folia groups nearby loaded chunks into regions, and each region gets its own independent tick loop, running at the standard 20 TPS. Regions merge or split automatically based on player movement and server load, so a cluster of players exploring together stays in one region while a lone player far away gets their own. There's no thread permanently assigned to a region; regions share threads from a configurable pool, and the tick loops for different regions run in genuine parallel across that pool. In PaperMC's framing, a server with players spread across the map creates many spread-out regions ticking in parallel, and that scales meaningfully better than ticking one world sequentially regardless of how spread out those players are.

The rule that makes this safe, and the one every plugin author has to internalize, is strict: regions tick in parallel, not concurrently, and they never share data. Code running in one region cannot access or modify data in another region. That's exactly the race condition the single-thread model existed to prevent, and Folia's answer isn't "make everything thread-safe." It's "make sure nothing ever needs to cross the boundary." One way to picture the shift: on regular Paper, the entire server, every world, every chunk, is effectively one giant region. Folia turns that one giant region into many smaller ones, each ticking on its own.

How the regionizer keeps regions from stepping on each other

PaperMC's own internal documentation defines a region formally: a set of owned chunk positions plus an implementation-defined data object tied to that region, with the guarantee that no two live regions ever own the same chunk position. The class responsible for creating, merging, and splitting regions, ThreadedRegionizer, enforces four invariants that let regions tick in parallel without race conditions:

  1. A ticking region can't grow while it's ticking. This stops two ticking regions from fighting over the same nearby chunk mid-tick.
  2. A ticking region owns a buffer of chunks outside its own perimeter, so it can safely read and create data nearby without touching another region's territory.
  3. A region can't begin ticking if it has a neighboring region that's also ticking. Adjacency during ticking is exactly what causes cross-region data races, so the regionizer refuses to start a tick until neighbors are clear.
  4. Adjacent regions merge into one. Two regions close enough to interfere become one region rather than staying separate and racing each other.
    Each region also carries one of four states: transient (not eligible to tick, typically a buffer region created around a ticking neighbor), ready (eligible to be picked up for the next tick), ticking, or dead (empty and marked for cleanup). When a chunk gets added near an existing region, PaperMC's regionizer either folds it into that region directly, or, if multiple nearby regions would need to combine, merges them into whichever one isn't currently mid-tick. If the only nearby candidate is actively ticking, the merge queues as a "merge later" and completes once that region finishes its current tick, which is how the system avoids ever mutating a region while another thread is in the middle of ticking it.

Regions don't share a clock either

Independent ticking means independent scheduling as well as independent execution. Each region maintains its own repeating task on a scheduled thread pool and targets 20 TPS on its own timeline, using a scheduling approach similar to earliest-deadline-first: if region A falls behind because a redstone contraption spiked its tick time, region B's schedule is untouched. A region that takes 15ms to tick keeps ticking every 50ms regardless of what its neighbors are doing, as long as the shared thread pool isn't fully saturated.

That independence extends to Minecraft's own tick counters. Vanilla tracks a Current Tick (ticks since server boot), a Game Time Tick (used to schedule redstone and physics events), and a Daylight Time Tick (ticks since noon). Folia splits these apart: Current Tick and a new Redstone Time counter are tracked per region, while Global Game Time and Daylight Time are tracked by a separate global region, a single always-running task at 20 TPS that owns anything not tied to a specific place: game rules, weather, the world border, and console commands. When two regions merge, one region's tick counters get an offset applied so that anything scheduled by relative deadline, a redstone timer, for instance, still fires at the correct relative time rather than jumping forward or backward.

We recommend reading $5 VPS Self-Hosting: What Actually Fits in 1GB for the flip side of this article's hardware math. That piece explains why its CPU guidance favors clock speed over core count, and what would change if you were sizing hardware for Folia instead of Paper.

What the scheduler APIs look like in code

The strict no-shared-data rule requires new plugin APIs, since the old Bukkit Scheduler assumed one main thread. A plugin targeting both Paper and Folia has to check which one it's running on before deciding how to schedule anything. PaperMC's own developer docs give the check as a small utility method:

private static boolean isFolia() {
    return ServerBuildInfo.buildInfo().isBrandCompatible(Key.key("papermc", "folia"));
}

A plugin also has to opt in explicitly, in paper-plugin.yml or plugin.yml:

folia-supported: true

That flag alone does nothing for safety. It only tells Folia's loader the plugin author has read the rest of this list and adjusted the plugin's code accordingly. The actual work happens in which scheduler a given task goes through:

RegionScheduler runs a task on the region that owns a specific location, not an entity. PaperMC's example is deliberately mundane, setting a block:

Location locationToChange = ...;
RegionScheduler scheduler = server.getRegionScheduler();
scheduler.execute(plugin, locationToChange, () -> {
    locationToChange.getBlock().setType(Material.BEEHIVE);
});

The location gets passed in specifically so the scheduler can work out which region currently owns it before running the task on that region's thread. The published API surface for RegionScheduler is two overloads of the same idea, one taking a Location and one taking a World plus chunk coordinates directly:

public interface RegionScheduler {
    void execute(Plugin plugin, World world, int chunkX, int chunkZ, Runnable run);
    default void execute(Plugin plugin, Location location, Runnable run) { ... }
}

EntityScheduler is the one every plugin author gets warned about separately, because it's easy to reach for RegionScheduler out of habit and get it wrong. An entity can move between regions; a location can't. PaperMC's docs are explicit that region schedulers should never be used for entity operations, since the entity scheduler "follows" the entity across a region boundary and the region scheduler does not:

EntityScheduler scheduler = entity.getScheduler();

AsyncScheduler covers work that never needs to touch region-owned data at all, database calls, HTTP requests, file I/O, the kind of thing that already ran off the main thread on regular Paper:

AsyncScheduler asyncScheduler = server.getAsyncScheduler();

GlobalRegionScheduler is for the small set of things Folia keeps server-wide rather than per-region: game rules, console commands, anything the global region (described above) owns:

GlobalRegionScheduler globalScheduler = server.getGlobalRegionScheduler();

Run through Paper instead of Folia, all four of these schedulers still work, PaperMC implements them internally to behave the same way a plugin would expect on a single-threaded server. That's what makes it possible to write one code path that works on both, instead of maintaining a Folia-specific fork of a plugin.

The real cost, and who this is for

Every plugin needs at least some review, even if no code changes. Folia won't load a plugin unless its author explicitly added folia-supported: true to the plugin's metadata, specifically so server owners aren't caught by silent, hard-to-diagnose breakage from a plugin assuming a main thread that no longer exists.

Hardware requirements shift in the opposite direction from everything else in this article. PaperMC's own guidance recommends at least 16 physical cores, not threads, to see a meaningful benefit. The team's rough starting formula for a ~300-player server: roughly 4 netty I/O threads and 3 chunk-system I/O threads per 200-300 players, 2 more chunk-system worker threads per 200-300 players if the world is pre-generated (chunk generation on an ungenerated world is its own bottleneck and doesn't have a clean per-player estimate), plus dedicated GC threads. Whatever cores remain, up to 80% of total CPU threads, get allocated to region tick threads; PaperMC caps it there deliberately, since plugins and the server itself can spin up threads you didn't explicitly budget for.

Version support starts at 1.20.4. An older world on an older version for compatibility reasons means Folia isn't an option regardless of hardware.

Some vanilla commands are disabled outright, not degraded: bossbar, clone, data, datapack, debug, function, item, loot, reload, return, ride, rotate, schedule, scoreboard, spectate, spreadplayers, tag, team, teammsg, tick, trigger, perf, saveall, and restart. Anything that assumes a single global world state to operate on doesn't have an obvious region-safe equivalent yet.

Migration is not a drop-in swap. World files carry over; Folia reads the same world format. Plugins don't carry over unmodified. People who've run large migrations put real debugging time at two to four weeks for a server with an established plugin ecosystem, and that's before accounting for plugins with no Folia-compatible fork at all.

The target audience PaperMC itself points to is servers that naturally spread players across a large world, skyblock and SMP being the commonly cited genres, with a sizeable player count and a CPU that's sitting idle despite lag because vanilla ticking can only use one core of it. Two production examples at that scale: DonutSMP runs multiple Folia instances to support an SMP network managing thousands of concurrent players and reportedly upward of 460,000 entities across its clusters, and 2b2t, the long-running anarchy server with a history of chronic low TPS, moved onto Folia to stabilize a version upgrade that had previously looked unworkable at its scale.

What's out of scope

Folia does not make Minecraft's world simulation thread-safe in general. It creates a specific, narrow set of cases (region-local data, scheduled cross-region tasks) where concurrent access is safe, and everything else remains off-limits by design. Specifically, Folia does not:

  • Speed up a single player's or a small group's experience. Regions only help when there's more than one region to parallelize across. A small server with everyone clustered near spawn is, for scheduling purposes, one region, and gets none of the multithreading benefit.
  • Fix single-region bottlenecks. A redstone-heavy build or an overloaded farm still runs on one thread, because it's inside one region. Folia isolates lag to that region instead of the whole server, but it doesn't make the region itself faster.
  • Run unmodified plugins. The folia-supported: true gate is mandatory, not optional, and plugins that touch the old Bukkit Scheduler assumptions need rework beyond that metadata flag.
  • Replace G1GC as the default recommendation. PaperMC's own testing notes that G1GC with Aikar's flags remains the right choice for the large majority of Folia servers; alternative collectors were tested under conditions (hundreds of gigabytes of RAM on enterprise hardware) that don't reflect typical deployments.
  • Guarantee more players before a network stops needing to shard. Folia raises the ceiling on a single instance considerably, but very large networks with existing sharding infrastructure aren't necessarily better served by replacing that infrastructure with one bigger Folia instance.

What this means for picking server software

Running Paper, Purpur, or Pufferfish for a normal-sized server, friends and family, a modest community, means nothing here is a reason to switch. Those platforms already capture most of the performance headroom available inside the single-thread model, and that model isn't the bottleneck at that scale. Folia solves a problem most servers don't have, at a real cost in plugin compatibility and setup complexity, and PaperMC saying so directly about its own project is worth taking at face value.

Folia earns consideration in the specific case above: real evidence the CPU is idling despite lag, players spread across a large world, and a plugin ecosystem small or flexible enough to survive the compatibility work. For everyone else, understanding why Folia exists is more useful than adopting it. It's the clearest illustration of what Paper and its forks have spent years optimizing around, and why "buy a faster single core, not more cores" has been correct advice for as long as it has.