Keystone

A shared framework for Minecraft plugins: config, messages, commands, scheduling, GUIs, registries and multi-version compatibility.

Minecraft
1.18-26.x
Servers
Spigot, Paper, Paper forks, Folia
Bytecode
Java 17
Licence
MIT

Why it exists

Both plugins needed the same things: a config that reloads safely, messages that cannot be turned into markup by a player's input, a command tree that enforces its own permissions, and scheduling that works on Folia. Writing those twice is how the two copies drift apart.

The compatibility story is the part that is hard to retrofit. Keystone is one module per API level, all emitting Java 17 bytecode:

TierCompiled againstUsed on
basespigot-api 1.20.41.18-1.20.4
modernspigot-api 1.21.11.20.5-1.21.3
latestpaper-api 26.1.21.21.4+
foliapaper-api 1.21.1Folia

Compiling each tier against its own API is what stops it calling a method the target range does not have. The split is enforced by the compiler rather than by discipline. Selection at runtime is always a capability probe, never a version comparison, because forks misreport versions and a probe tests the thing that actually matters.

Using it

public final class YourPlugin extends JavaPlugin {
    private KeystoneHandle keystone;

    @Override public void onEnable()  { keystone = Keystone.bootstrap(this); }
    @Override public void onDisable() { if (keystone != null) keystone.shutdown(); }
}

Subsystems hang off the handle. Most are created lazily; GUI click protection is registered during bootstrap so a menu cannot accidentally open without its anti-duplication listener. Call shutdown() from onDisable even after a partial enable so listeners, audiences, tasks and custom cleanup hooks are released.

Choose your path

GoalStart here
Add Keystone to a Maven pluginGetting started
Pick a library subsystemSubsystem map
Make Bukkit and Folia scheduling safeScheduling
Expose an addon-facing APIPublishing an API
Diagnose relocation or runtime failuresTroubleshooting

Where to go next

  • Getting started: the Maven setup and the relocation rule.
  • Subsystems: choose the part of the library you need.
  • Configuration and messages: reloadable YAML, snapshots and safe MiniMessage placeholders.
  • Commands: command trees, permissions and completion.
  • Scheduling: correct entity, region, global and async work on Bukkit and Folia.
  • GUIs and registries: inventory menus and owner-aware extension points.
  • Storage: SQLite/MySQL access and forward-only migrations.
  • Metrics: optional bStats and telemetry sinks.
  • Adventure: the two builds, and how to choose.
  • Publishing an API: the mistake that produces a NoClassDefFoundError naming a class that looks correct.
  • Troubleshooting: build, relocation, Folia and runtime failures.