New: see how many SpigotMC listing viewers end up running your plugin →

Guides · Engineering

How to make your Minecraft plugin Folia-compatible

Make your plugin Folia-compatible: folia-supported, the right Paper scheduler for each task, one jar for Spigot, Paper and Folia, and pitfalls.

Updated · by Skilled · 3 min read

Folia is PaperMC's fork that splits the world into regions and ticks them in parallel, on several threads. Big servers use it to hold far more players. For plugins, it removes the assumption almost every plugin makes: that there is one main thread.

Supporting it is mostly about scheduling. Here's what changes and how to keep a single jar that runs on Spigot, Paper and Folia.

What's different on Folia

  • There's no main thread. Each region of the world ticks on its own thread. The code that touches a block or an entity must run on the thread that owns its region.
  • BukkitScheduler doesn't work. Bukkit.getScheduler().runTask(...) and friends throw UnsupportedOperationException on Folia.
  • Touching the world from the wrong thread throws. Reading or changing a block, an entity or a player from another region's thread fails with an exception instead of quietly racing.
  • Teleporting is asynchronous. Use teleportAsync, which returns a future.

Declare support

Folia only loads plugins that say they support it, in plugin.yml:

folia-supported: true

Only add it once the plugin really works on Folia: owners trust that flag.

Pick the right scheduler

Paper's API (1.20 and later) has four schedulers that work on both Paper and Folia. Use the one that matches what the task touches:

The task touchesUseExample
One entity or playerentity.getScheduler()Give a player an item, update their boss bar
A location or chunkBukkit.getRegionScheduler()Change blocks in an arena, spawn at a location
Nothing in the world (global state)Bukkit.getGlobalRegionScheduler()Day counters, broadcasting, world time
Nothing in the world, and it's slowBukkit.getAsyncScheduler()Database, HTTP, files

For example, a repeating task per player:

player.getScheduler().runAtFixedRate(plugin, task -> {
    if (!player.isOnline()) { task.cancel(); return; }
    updateScoreboard(player);
}, null, 20L, 20L);

And going from async work back to a player:

Bukkit.getAsyncScheduler().runNow(plugin, t -> {
    Stats stats = database.load(player.getUniqueId());
    player.getScheduler().run(plugin, t2 -> apply(player, stats), null);
});

On Paper these run on the main thread as usual, so the same code works on both.

One jar for Spigot, Paper and Folia

Spigot doesn't have these schedulers. If you want one jar for everything, detect Folia at startup and route through a small wrapper:

public final class Platform {
    public static final boolean FOLIA = classExists("io.papermc.paper.threadedregions.RegionizedServer");

    private static boolean classExists(String name) {
        try { Class.forName(name); return true; } catch (ClassNotFoundException e) { return false; }
    }
}

Then one method per kind of task: on Folia it calls the right Paper scheduler, elsewhere BukkitScheduler. Keep every scheduling call in your plugin going through that wrapper, so a stray Bukkit.getScheduler() can't slip back in. Libraries like FoliaLib do this for you if you'd rather not write it.

Common mistakes

  • Iterating all players from one task and changing each of them. On Folia, schedule the change on each player's own scheduler.
  • Global managers that tick everything: one task that updates every arena in the world. Split it per region (one region task per arena) or move the computing part to the async scheduler.
  • Static caches shared between threads without synchronisation: what was safe with one main thread now races. Use concurrent collections.
  • Assuming Bukkit.isPrimaryThread() means "safe to touch anything". On Folia it doesn't.
  • Libraries. A dependency that uses BukkitScheduler breaks your plugin on Folia too. Check them, including the analytics or update-checker library you shade. (PluginAnalytics' SDK supports Folia with nothing to configure.)

Test it

Run your plugin on a real Folia server with a few players in different areas of the world, and use every feature. Problems show as exceptions in the console, which is good: they're loud. An error tracker that tags errors by platform (like PluginAnalytics) tells you whether an error only happens on Folia servers, which saves a lot of guessing once the plugin is out.

Skilled

Makes Minecraft plugins (including The Sift on SpigotMC) and PluginAnalytics, so other plugin developers can see how their plugins are found, installed and used.

More guides

Engineering

Which part of your Minecraft plugin causes lag (and how to fix it)

Find the part of your Spigot or Paper plugin that causes lag: MSPT, spark profiles, the usual suspects and how to see its cost across all servers.

4 min read
Engineering

Error tracking for Minecraft plugins: find the bug before the 1-star review

How Spigot and Paper plugin exceptions reach the console, why waiting for reports fails, and how to collect, group and fix errors from every server.

4 min read

See how your own plugin is found and used

Free for your first plugin. One line in onEnable.