Skip to content

Let your ideas bloom.

One elegant framework. Every generation of Minecraft development.

Write a plugin once with Bloom's menus, items, messages, config and storage. Bloom runs it on Spigot 1.8 and on Paper 26, and handles the differences behind version adapters.

Minecraft versions supported by BloomSupported: 1.7.10 and 1.8.x on Bukkit or Spigot with Java 8; Paper 1.21.x with Java 21; Paper 26.x with Java 25. Versions 1.9 to 1.21.0 have no adapter yet.1.7.10Java 81.8.xJava 81.21.xPaper, Java 2126.xPaper, Java 251.9 to 1.21.0: no adapter yet

The same jar on every server you support

Bukkit's API changed shape across a decade of releases: online players became a collection, inventory views became interfaces, materials were renamed, and text moved to components. Bloom compiles your plugin against the oldest API, detects the server at startup, and loads the adapter that speaks its dialect.

Switch the server on the right to see what Bloom reports when the same plugin starts.

[INFO] [ShopPlugin] Bloom 0.1.0 · Spigot 1.8.8 (Java 1.8.0_472) · adapter: legacy (Bukkit 1.7.10-1.8.9)[INFO] [ShopPlugin]   capabilities: ACTION_BAR, TITLES, ITEM_NBT_TAGS, ITEM_FLAGS, UNBREAKABLE_ITEMS, LEGACY_DATA_VALUES[INFO] [ShopPlugin]   unavailable:  ADVENTURE_TEXT, MINIMESSAGE, HEX_COLORS, TITLE_TIMINGS, BOSS_BAR, COMPONENT_INVENTORY_TITLES, PERSISTENT_DATA_CONTAINER, CUSTOM_MODEL_DATA

Legacy capabilities come from probes run at startup; this is the list when every probe succeeds.

A shop in thirty lines

Items use modern names everywhere: on 1.8, COOKED_BEEF and LIME_WOOL resolve to their legacy materials and data values. The action bar is sent where the server supports it and skipped with one warning where it does not.

This code is compiled by Bloom's build on every change, so it matches the current API.

ShopPlugin.javaView source
public final class ShopPlugin extends JavaPlugin {
    private Bloom bloom;

    @Override
    public void onEnable() {
        bloom = Bloom.enable(this); // detects 1.7.10 ... 26.x and loads the matching adapter
        Items items = Items.of(bloom);

        Menu shop = Menu.builder()
                .title("&dBlossom Shop")
                .rows(3)
                .button(
                        13,
                        Button.of(items.builder("COOKED_BEEF").name("&6Feed me").build(), click -> {
                            click.player().setFoodLevel(20);
                            bloom.messages().actionBar(click.player(), "&aYum!");
                            click.close();
                        }))
                .build();

        bloom.events()
                .subscribe(
                        PlayerJoinEvent.class,
                        event -> bloom.scheduler()
                                .syncLater(() -> Menus.of(bloom).open(event.getPlayer(), shop), 20));
    }

    @Override
    public void onDisable() {
        bloom.shutdown(); // closes menus, cancels tasks, unregisters listeners
    }
}

Compatibility

Supported ranges, and how each one has been verified so far. Bloom labels a version tested only after it has actually been run.

Supported Minecraft versions and how each was verified
MinecraftServerJavaBloomVerified by
1.7.10CraftBukkit, Spigot, PaperSpigot8Supportedlegacy adaptertested-real-server PaperSpigot 1.7.10, unit-testsNo titles or action bars (1.7 protocol).
1.8 - 1.8.9CraftBukkit, Spigot, PaperSpigot8Supportedlegacy adaptertested-real-server PaperSpigot 1.8.8, compile-verified 1.8.8, unit-testsTitles via Spigot API, action bars via reflection.
1.9 - 1.21.0any-Not supported—No adapter yet. Bloom refuses to start with an explanation.
1.21.1 - 1.21.11Paper and forks21Supportedmodern adaptertested-real-server Paper 1.21.11, compile-verified 1.21.1, compile-verified 1.21.11, tested-mockbukkit 1.21.11Spigot/CraftBukkit 1.21 are not supported: the modern adapter needs Paper's Adventure API.
26.1 - 26.3.xPaper and forks25Supportedmodern adaptertested-real-server Paper 26.3, compile-verified 26.3Same adapter as 1.21.
newer than 26.3.xPaper25+Opt-in, untestedmodern adapter—Only with BloomOptions.allowNewerUntestedVersions(true); Bloom logs a warning.
anyFolia-Not supported—Regionised threading needs a dedicated scheduler adapter (planned).

Updated 2026-10-13. “Supported” means Bloom starts and selects an adapter; the last column lists how that version has actually been verified so far.

Capabilities by version and Java requirements

Modules

Take only what you use. Each module is a small jar; the adapters are added at runtime only.

How the modules fit together
bloom-bukkit
Startup, platform detection, adapter loading, scheduler, messages and events.
bloom-gui
Inventory menus with layouts, pagination, back navigation and duplication-safe clicks.
bloom-items
Item builder, modern material names on every version, persistent custom item ids.
bloom-config
YAML with defaults, validation that names the line, migrations and comment-safe saves.
bloom-storage
SQLite, MySQL and MariaDB off the main thread, with transactions and migrations.
bloom-testing
A tick-by-tick scheduler and controllable clock for plugin unit tests.
bloom-legacy
Adapter for Bukkit and Spigot 1.7.10 to 1.8.9 on Java 8.
bloom-modern
Adapter for Paper 1.21.1 to 26.3.x with Adventure and MiniMessage.

Errors that tell you what to do

Bloom's exceptions name the file, the line, the server and the fix. A typo in a config value reads like this, not like a stack trace:

config.yml line 7: 'homes.max-homes' expected a whole number but found 'ten'

Missing an adapter? Bloom names the dependency to add. Calling a database on the main thread? It says which executor to use instead.

Built for coding agents too

Every public type and method is published as a machine-readable manifest generated from the source, alongside an llms.txt index and a Claude Code skill that teaches agents to check the installed Bloom version, pick the right adapter and avoid inventing methods.

What Bloom promises

Honest about versions

Bloom refuses to start on a server it has no adapter for and tells you why. Features a version lacks are reported as capabilities you can check, never faked.

The main thread stays free

Database work runs on a bounded pool and fails fast when saturated. Blocking calls on the main thread throw with a message that says how to switch threads.

Nothing left running

Tasks, listeners, menus, boss bars and database pools belong to your plugin and are closed in order when it shuts down.

Items stay in menus

Every click and drag inside a Bloom menu is cancelled, including shift-clicks and number keys, and re-cancelled if another plugin interferes.

Get started

  1. Pick your oldest server

    Compile against spigot-api 1.8.8 for one jar that runs everywhere, or against Paper for modern-only plugins.

  2. Add Bloom

    Depend on the feature modules you use and add the adapters for the versions you support.

  3. Relocate

    Shade Bloom into your plugin under your own package so plugins never conflict.

  4. Enable it

    Call Bloom.enable(this) in onEnable and bloom.shutdown() in onDisable.

Follow the installation guide
build.gradle.ktsView source
repositories {
    mavenCentral()
    // Bloom is not on Maven Central yet. Either unzip bloom-<version>-central-bundle.zip from GitHub Releases and add
    // maven(uri("path/to/unzipped-bundle")), or run `./gradlew publishToMavenLocal` in the Bloom repository.
    mavenLocal()
    maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") // spigot-api
    // spigot-api 1.8.8 depends on bungeecord-chat 1.8-SNAPSHOT, which only PaperMC's repository still serves.
    maven("https://repo.papermc.io/repository/maven-public/")
}

java {
    toolchain { languageVersion = JavaLanguageVersion.of(21) }
    // The plugin is Java 8 bytecode but bundles Bloom's Java 21 modern adapter, which Bloom only loads on Java 21+.
    disableAutoTargetJvm()
}

val bloomVersion = providers.gradleProperty("bloomVersion").getOrElse("0.1.0")

dependencies {
    compileOnly("org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT") // compile against the oldest API you support
    implementation("io.github.crownalonedev.bloom:bloom-gui:$bloomVersion") // menus + items + core
    runtimeOnly("io.github.crownalonedev.bloom:bloom-legacy:$bloomVersion") // 1.7.10 - 1.8.9
    runtimeOnly("io.github.crownalonedev.bloom:bloom-modern:$bloomVersion") // Paper 1.21.1 - 26.3.x
}

tasks.withType<JavaCompile>().configureEach {
    options.release = 8
}

tasks.shadowJar {
    archiveClassifier = ""
    // Relocate Bloom into your own package so several plugins can embed different Bloom versions.
    // The trailing dots matter: they stop the pattern from matching unrelated packages.
    relocate("io.github.crownalonedev.bloom.", "com.example.myplugin.libs.bloom.")
}

Bloom 0.1.0 is not on Maven Central yet; the installation guide shows how to use the release from GitHub.