---
title: "Migrate a Minecraft Plugin from 1.21 to 26.x: 2026 Guide | Minax"
description: "Calendar versioning, Adventure 5, the remapper disappearing, the libraries block replacing shading: everything you must change to migrate a plugin from 1.21 to 26.x, and which version to actually target."
lang: en-US
json-ld: |
  [
    {
      "@context": "https://schema.org",
      "@graph": [
        {
          "@type": "Organization",
          "@id": "https://minax.fun/#organization",
          "name": "Minax",
          "url": "https://minax.fun",
          "logo": {
            "@type": "ImageObject",
            "url": "https://minax.fun/minax-logo-256.png",
            "width": 256,
            "height": 256
          },
          "description": "Plateforme IA pour créer des plugins Minecraft (Paper, Spigot, Bukkit, Folia) sans coder en Java."
        },
        {
          "@type": "WebSite",
          "@id": "https://minax.fun/#website",
          "url": "https://minax.fun",
          "name": "Minax",
          "publisher": {
            "@id": "https://minax.fun/#organization"
          },
          "inLanguage": [
            "fr-FR",
            "en-US",
            "de-DE"
          ],
          "potentialAction": {
            "@type": "SearchAction",
            "target": "https://minax.fun/discover?q={search_term_string}",
            "query-input": "required name=search_term_string"
          }
        }
      ]
    },
    [
      {
        "@context": "https://schema.org",
        "@type": "Article",
        "headline": "Migrating a Minecraft plugin from 1.21 to 26.x: the full guide",
        "description": "Minecraft moved to calendar versioning. Here is exactly what to change in a plugin to go from 1.21 to 26.x, and which version to actually target given the real server population.",
        "image": "https://minax.fun/og-image.jpg",
        "datePublished": "2026-07-25",
        "dateModified": "2026-07-25",
        "inLanguage": "en",
        "author": {
          "@type": "Organization",
          "name": "Minax",
          "url": "https://minax.fun"
        },
        "publisher": {
          "@type": "Organization",
          "name": "Minax",
          "url": "https://minax.fun"
        },
        "mainEntityOfPage": "https://minax.fun/blog/migrate-minecraft-plugin-version"
      },
      {
        "@context": "https://schema.org",
        "@type": "FAQPage",
        "mainEntity": [
          {
            "@type": "Question",
            "name": "Should I drop 1.21 now that 26.2 is out?",
            "acceptedAnswer": {
              "@type": "Answer",
              "text": "No. On a bStats sample of 62,948 servers, 1.21.11 holds 36.4% and 1.21.4 still holds 7.2%, against 14.9% for 26.1.2 and 7.4% for 26.2. The 1.21 family is still the majority, so dropping its compatibility costs you more servers than it wins."
            }
          },
          {
            "@type": "Question",
            "name": "Do calendar versions require compiling against Java 25?",
            "acceptedAnswer": {
              "@type": "Answer",
              "text": "Java 25 is the runtime floor for calendar versions, but Java 21 is still the right compile target: a Java 21 .jar loads on a newer runtime, while a Java 25 .jar refuses to load on a Java 21 server and throws UnsupportedClassVersionError."
            }
          },
          {
            "@type": "Question",
            "name": "Why does my message code no longer compile on Paper 26.x?",
            "acceptedAnswer": {
              "@type": "Answer",
              "text": "Paper 26.x ships Adventure 5, which removed APIs that were previously only deprecated. Anything touching messages and text shown to players is affected. The clean exit is to move to Component everywhere, which also compiles on 1.21."
            }
          },
          {
            "@type": "Question",
            "name": "Do I still need to bundle the SQLite driver in my .jar?",
            "acceptedAnswer": {
              "@type": "Answer",
              "text": "No, the SQLite JDBC driver already ships with Paper. HikariCP does not: declare it in the libraries block of plugin.yml instead of shading it."
            }
          }
        ]
      },
      {
        "@context": "https://schema.org",
        "@type": "BreadcrumbList",
        "itemListElement": [
          {
            "@type": "ListItem",
            "position": 1,
            "name": "Blog",
            "item": "https://minax.fun/blog"
          },
          {
            "@type": "ListItem",
            "position": 2,
            "name": "Migrate a plugin to a new version",
            "item": "https://minax.fun/blog/migrate-minecraft-plugin-version"
          }
        ]
      }
    ]
  ]
---

[Skip to content](#main-content)

[Minax](/)

15 online 

[Plugins](/discover)[Blog](/blog)[Pricing](/pricing)

en 

Sign InSign Up

[Blog](/blog)/ Migration 

July 25, 2026 · Migration 

# Migrating a plugin from 1.21 to 26.x: what breaks, and what you should actually target

![Minecraft server console during a plugin migration to a calendar version](/assets/blog-minecraft-update-DQC6XZwv.webp)

Minecraft no longer counts in 1.x. The latest version is called 26.2 and it shipped on June 16, 2026. If you maintain a plugin written for 1.21, the interesting question is not only technical, it is strategic. Migrating costs time, and targeting the wrong version costs installs. This guide covers exactly what to change in the code, in which order, and above all which version to aim at, because the right answer is not the newest one.

## What calendar versioning actually changed 

The version number now encodes the year and the release rank inside that year. 26.2 means the second release of 2026, nothing more. The number no longer tells you whether a change is cosmetic or structural, and that is the first habit to unlearn: you cannot size a migration from the version jump any more. You have to look at what moved underneath.

Underneath, three things moved and all three matter. Minecraft has been deobfuscated since 26.1, which made Paper's remapper disappear. Paper 26.x ships Adventure 5, which removed APIs that had previously only been deprecated. And Java 25 became the runtime floor for calendar versions. A 1.21 plugin that touches any of those three areas does not simply recompile.

The number almost nobody checks before migrating

On a bStats sample of **62,948 servers**, the real distribution is: 1.21.11 at **36.4%**, 26.1.2 at **14.9%**, 26.2 at **7.4%**, 1.21.4 at **7.2%**. Those two 1.21 entries alone account for 43.6% of the population, against 22.3% for both calendar branches combined. The installed base is nearly twice as much 1.21 as calendar. [bStats](https://bstats.org) publishes these distributions continuously, and it is the only serious measurement available.

## Which version to actually target 

There is one more trap. Paper 26.2 has no stable build: the sensible production target on the calendar branch is 26.1.2, not 26.2. Shipping a plugin that only exists for 26.2 means asking your users to run a base that Paper itself does not call stable.

Share of servers

Verdict

1.21.11

36.4%

Primary target, most of your installs live here

26.1.2

14.9%

Calendar production target, the one to test on

26.2

7.4%

No stable Paper build, do not ship for it alone

1.21.4

7.2%

Free compatibility if you stay on the public API

My reading, stated plainly: dropping 1.21 today would be a mistake. That is not nostalgia, it is arithmetic. You would give up the majority family to gain a branch that weighs half as much, whose newest release does not even have a stable build. The right target is a single plugin that compiles against 1.21, loads on 26.x, and uses nothing that vanished between the two.

> "You do not migrate to the latest version, you migrate to the version the people installing your plugin are actually running."
> 
> **Migration rule**, true for the whole Bukkit ecosystem 

## API version and Java version 

Two separate settings that get confused constantly. The API version is the generation of the Bukkit API your code is written against, declared in plugin.yml. The Java version is the bytecode format your compiler emits. They are set independently, and getting the second one wrong is the most mundane cause of a plugin refusing to load.

pom.xml 

```
<properties>
  <!-- 21, not 25: Java 21 bytecode loads on a newer runtime.
       The reverse is not true. -->
  <maven.compiler.release>21</maven.compiler.release>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<repositories>
  <repository>
    <id>papermc</id>
    <url>https://repo.papermc.io/repository/maven-public/</url>
  </repository>
</repositories>

<dependencies>
  <dependency>
    <groupId>io.papermc.paper</groupId>
    <artifactId>paper-api</artifactId>
    <version>1.21.11-R0.1-SNAPSHOT</version>
    <scope>provided</scope>
  </dependency>
</dependencies>
```

Compile against the _oldest_ version you intend to support, not the newest. A plugin compiled against the 26.x API and installed on a 1.21 server dies the moment it calls a method that does not exist there yet. The other way round, a plugin compiled against 1.21 runs on 26.x as long as it avoids removed APIs. That asymmetry is your friend. Exact coordinates per branch are on [docs.papermc.io](https://docs.papermc.io), and the signatures are on [jd.papermc.io](https://jd.papermc.io).

Java 25 is a runtime floor, not a compile target

Java 25 is the floor for calendar versions. That means a 26.x server runs on Java 25 or newer, not that your plugin must be compiled for 25. If you bump your compile target to 25, your .jar will refuse to load on the entire 1.21 family still running Java 21, with an `UnsupportedClassVersionError` to show for it. Stay on 21 unless you genuinely need a specific language feature, which is rare inside a plugin.

## Adventure 5: text is the blast radius 

This is where most of the damage happens. Paper 26.x ships Adventure 5, and Adventure 5 removed APIs that until then were only deprecated. The affected surface is predictable: everything to do with messages and text displayed to players. A 1.21 plugin that sends messages the old way may simply stop compiling.

The good news is that there is one exit and it works in both worlds. Adventure has been included in Paper for a long time, so code written with `Component` compiles on 1.21 and on 26.x alike. You are not maintaining two versions, you are modernising once.

Java 

```
package fun.example.migration;

import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;

public final class Messages {

    private static final MiniMessage MINI = MiniMessage.miniMessage();

    private Messages() {
    }

    /** Built in code, with no colour strings anywhere. */
    public static Component welcome(String playerName) {
        return Component.text("Welcome ", NamedTextColor.GOLD)
                .append(Component.text(playerName, NamedTextColor.WHITE))
                .append(Component.text(" to the server.", NamedTextColor.GRAY));
    }

    /** Same idea for a number, without coloured concatenation. */
    public static Component balance(long amount) {
        return Component.text("Balance: ", NamedTextColor.GRAY)
                .append(Component.text(amount, NamedTextColor.GOLD));
    }

    /** A line the admin wrote in config.yml, in MiniMessage format. */
    public static Component fromConfig(String line) {
        return MINI.deserialize(line);
    }

    /**
     * A legacy string inherited from an older config.yml, such as "&aGreen text".
     * Convert it instead of dropping it: those files already exist on real
     * servers and nobody is going to rewrite them by hand.
     */
    public static Component fromLegacy(String stored) {
        return LegacyComponentSerializer.legacyAmpersand().deserialize(stored);
    }
}
```

At the call site nothing clever is needed: `player.sendMessage(Messages.welcome(player.getName()))`. Adventure documentation lives on [docs.advntr.dev](https://docs.advntr.dev), and the Paper side of the integration on [docs.papermc.io](https://docs.papermc.io).

What does not work: find and replace

The temptation is to sweep mechanically through every message call until the compiler goes quiet. Two classic casualties. One: you delete the code that reads legacy strings from `config.yml`, and every server that had customised its messages loses them on first boot. Two: you miss the places that still compile but change appearance in game, such as item names, inventory titles and scoreboard lines. Inventory those three surfaces by hand before you touch the code.

## The remapper is gone 

Minecraft has been deobfuscated since 26.1, and Paper's remapper went away with the obfuscation. If your plugin stayed politely on the public Bukkit and Paper API, you will notice nothing at all. If it reached down into the server's internal classes, this part hits you head on: the remapping step your build relied on no longer exists, and the names you were reflecting over are no longer the same names.

My advice here is blunt and I stand by it: use the migration as the excuse to get out of internals. Reflection into internal classes is exactly the kind of code that demands a fix every single version, calendar or not. Most of the time the public API now does the same job. When it genuinely does not, isolate that access in one class with a clean fallback if the expected class is missing, instead of scattering it through the plugin.

What deobfuscation gives you

No remapping step in the build any more

Readable stack traces in crash reports

Readable internals when you need to understand a behaviour

What it breaks

Builds that depended on the remapper stop working

Reflection on old obfuscated names fails

Every NMS tutorial written before 26.1 is stale

## The libraries block instead of shading 

The historical reflex was to bundle your dependencies inside the .jar, in other words to shade them. The `libraries:` block in plugin.yml replaces that practice: you declare the dependency and the server downloads it itself. The .jar goes back to being small and containing only your code.

Before you declare anything, check what is already there. The SQLite JDBC driver already ships with Paper, so if you were bundling it, you were paying weight for nothing. HikariCP, on the other hand, is not shipped. That is exactly the kind of dependency `libraries:` exists for.

plugin.yml 

```
name: MyPlugin
version: 1.0.0
main: fun.example.migration.MyPlugin
api-version: '1.21'
description: Migrated plugin, one .jar for 1.21 and 26.x
authors: [ MyName ]

# Downloaded by the server on first startup.
# The SQLite driver is NOT here: Paper already provides it.
libraries:
  - com.zaxxer:HikariCP:5.1.0

commands:
  balance:
    description: Show your balance
    usage: /balance
```

The `api-version` value declares the API generation your code is written against. Keep it on the oldest generation you truly support: that is what keeps you loadable across the whole 1.21 family while still being accepted by newer servers. The file format is documented on [docs.papermc.io](https://docs.papermc.io) and, for the historical Bukkit side, on [hub.spigotmc.org](https://hub.spigotmc.org).

What does not work: assuming libraries is free

Two very concrete traps. First, the download happens on the server machine at first startup: a host that blocks outbound network traffic makes your plugin fail to load, and the user will have no idea why. Second, a mistyped coordinate is invisible at compile time. Your build passes, your .jar is valid, and the error only surfaces when the user's server boots. Always test the .jar on a real server before publishing, not just in your IDE.

## The offline-mode UUID trap 

Here is the part almost no migration guide covers, and it is the one that destroys the most data. **75.1% of servers run in offline mode.** In offline mode a player's UUID is derived from their username. That is not an implementation detail, it is a property that changes how you design your database: if the player renames, or if the server ever switches to online mode, the key changes and every row stored under the old UUID is orphaned.

Concretely, an economy plugin that stores a balance against a UUID and nothing else hands its users this scenario: the renamed player loses their money, the admin has no idea what happened, and nobody can reconstruct the mapping. The fix is one column: keep the username next to the UUID, index it, and you keep a path back.

SQL 

```
CREATE TABLE IF NOT EXISTS player_data (
  uuid       TEXT    PRIMARY KEY,
  last_name  TEXT    NOT NULL,
  balance    INTEGER NOT NULL DEFAULT 0,
  updated_at INTEGER NOT NULL
);

-- Mandatory in offline mode: it is the only route back when a player
-- renames, or when the server flips to online mode.
CREATE INDEX IF NOT EXISTS idx_player_data_name ON player_data(last_name);
```

Java 

```
package fun.example.migration;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.UUID;

public final class PlayerRepository {

    private final Connection connection;

    public PlayerRepository(Connection connection) {
        this.connection = connection;
    }

    /**
     * Always write the current username alongside the data.
     * Without that line, a rename makes the row unreachable on the
     * 75.1% of servers running in offline mode.
     */
    public void save(UUID uuid, String name, long balance) throws SQLException {
        String sql = "INSERT INTO player_data(uuid, last_name, balance, updated_at) "
                + "VALUES(?, ?, ?, ?) "
                + "ON CONFLICT(uuid) DO UPDATE SET "
                + "last_name = excluded.last_name, "
                + "balance = excluded.balance, "
                + "updated_at = excluded.updated_at";
        try (PreparedStatement ps = connection.prepareStatement(sql)) {
            ps.setString(1, uuid.toString());
            ps.setString(2, name);
            ps.setLong(3, balance);
            ps.setLong(4, System.currentTimeMillis());
            ps.executeUpdate();
        }
    }

    public long readBalance(UUID uuid) throws SQLException {
        String sql = "SELECT balance FROM player_data WHERE uuid = ?";
        try (PreparedStatement ps = connection.prepareStatement(sql)) {
            ps.setString(1, uuid.toString());
            try (ResultSet rs = ps.executeQuery()) {
                return rs.next() ? rs.getLong(1) : 0L;
            }
        }
    }
}
```

The test I run every time

On a test server in offline mode: log in, create some data, log out, rename your test account, log back in. If your data is gone, your schema is fragile and it already was before the migration. The test takes two minutes and it exposes a flaw that three quarters of the server population can trigger.

## Folia: the main thread is no longer a given 

Folia runs world regions across several threads. A plugin that assumes a single main thread does not get slower, it crashes. The migration means going through the RegionScheduler to touch the world, and never running database access on a game thread, or your TPS pays for it.

Java 

```
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;

import java.sql.SQLException;
import java.util.UUID;

public final class BalanceLookup {

    private final Plugin plugin;
    private final PlayerRepository repository;

    public BalanceLookup(Plugin plugin, PlayerRepository repository) {
        this.plugin = plugin;
        this.repository = repository;
    }

    public void showBalance(UUID uuid, Location location) {
        // 1. The database, off every game thread.
        Bukkit.getAsyncScheduler().runNow(plugin, task -> {
            final long balance;
            try {
                balance = repository.readBalance(uuid);
            } catch (SQLException e) {
                plugin.getLogger().warning("Could not read balance: " + e.getMessage());
                return;
            }

            // 2. Back to the world, on the thread that owns this region.
            Bukkit.getRegionScheduler().execute(plugin, location, () -> {
                Player player = Bukkit.getPlayer(uuid);
                if (player != null) {
                    player.sendMessage(Messages.balance(balance));
                }
            });
        });
    }
}
```

This code is not Folia only: Paper exposes those schedulers, so you can write it today against your 1.21 target and stay compatible without a second codebase. Folia behaviour is documented on [docs.papermc.io](https://docs.papermc.io), and if you want to see the shape of the result without setting up the environment, our [Folia plugin generator](/folia-plugin-generator) emits RegionScheduler code directly.

## My call, stated plainly 

A neutral overview helps nobody, so here is what I recommend.

**One .jar for the vast majority of cases.** Compiled for Java 21, against the 1.21 API, zero internal access, Adventure everywhere, Folia-compatible schedulers. That plugin covers the majority 1.21 family and loads on 26.x. Two separate builds are only justified if you genuinely touch the server's internal classes.

**Folia: on a modest server, I do not migrate.** The RegionScheduler makes the code harder to read for a gain you will not measure when a single region carries everyone. On a busy server, or on a very spread-out map where players are scattered, the investment starts paying off. Write the compatible code from the start anyway: it costs nothing on Paper and saves you a rewrite the day your scale changes.

**Database: SQLite for as long as you have a single server.** The driver is already provided, one connection is enough, and you declare nothing at all. The day several servers must share the same data, you move to MySQL, and only then does HikariCP belong in the `libraries:` block. A connection pool over a local SQLite file is complexity for its own sake.

**26.2: I ship nothing for it.** While there is no stable Paper build, the calendar target is 26.1.2. Test on 26.2 out of curiosity if you like, but do not build your compatibility story on it.

### Do the migration without reading every line yourself

Describe your plugin and the version you target. Minax writes Adventure-based code, compiles a clean .jar and lets you test it online before installing.

[Migrate my plugin](/minecraft-plugin-maker)

## The migration checklist, in order 

1.  Fix the target: compile against 1.21, verify loading on 26.1.2.
2.  Check that `maven.compiler.release` is 21, not 25.
3.  List every place that produces text: messages, item names, inventory titles, scoreboards.
4.  Move those places to `Component`, and add a conversion for legacy config.yml strings.
5.  Hunt down every access to the server's internal classes, then delete it or isolate it in one class.
6.  Strip from the .jar anything Paper already provides, starting with the SQLite JDBC driver.
7.  Declare what is left in `libraries:` instead of shading it.
8.  Move every database access off the game threads.
9.  Replace single-main-thread assumptions with the RegionScheduler.
10.  Add the username column next to the UUID, then run the offline-mode rename test.
11.  Install the .jar on a real server, once on 1.21 and once on 26.1.2, before publishing.

If a step breaks, read the first error line and nothing else: our [plugin troubleshooting guide](/blog/erreurs-plugin-minecraft) covers precisely the messages this kind of migration produces.

## Conclusion 

Calendar versioning made the targeting question more visible, not simpler. The real migration work is four jobs: text through Adventure, getting out of internals, dependencies declared rather than bundled, and dropping the single-thread assumption. Everything else is configuration. And the most important decision is not technical: while 1.21 is worth nearly twice the calendar population, the version to target is still 1.21, with code that also runs on 26.x. For what changed on the version side, our [Minecraft 26 plugins](/minecraft-26-plugins) page details the calendar branch.

## Articles connexes

[

![Minecraft 26 plugins: what changed](/assets/blog-minecraft-update-DQC6XZwv.webp)

Versions 

### Minecraft 26 plugins: what changed

Lire ](/minecraft-26-plugins)[

![My plugin will not load: troubleshooting](/assets/blog-optimiser-serveur-D9PGend8.webp)

Troubleshooting 

### My plugin will not load: troubleshooting

Lire ](/blog/erreurs-plugin-minecraft)[

![Generate a Folia plugin](/assets/blog-create-plugin-1g5qWpiS.webp)

Tool 

### Generate a Folia plugin

Lire ](/folia-plugin-generator)

12 min read 

Sommaire

[Calendar versioning](#calendrier)[Which version to target](#parc)[API version and Java](#api)[Adventure 5 and text](#adventure)[The remapper is gone](#remapper)[libraries instead of shading](#libraries)[The offline-mode UUID trap](#uuid)[Folia and the main thread](#folia)[My call, stated plainly](#avis)[The migration checklist](#checklist)[Conclusion](#conclusion)

Partager

Lien[X](https://twitter.com/intent/tweet?url=https%3A%2F%2Fminax.fun%2Fblog%2Fmigrate-minecraft-plugin-version)

Minax 

The specialized AI that revolutionizes Minecraft plugin creation. Transform your ideas into professional code in minutes.

**408** Plugins created **0** Members 

#### Product

-   [Plugin generator](/)
-   [Create a plugin](/creer-plugin-minecraft)
-   [Spigot Generator](/generateur-spigot)
-   [User reviews](/discover)

#### Resources

-   [Blog & Resources](/blog)
-   [Minecraft Server Guide](/guide-serveur-minecraft)
-   [Host your server](/hebergeur-serveur-minecraft)
-   [SMP mechanics](/smp-plugins-minecraft)
-   [Discord Community](https://discord.gg/NG4nR3E5a6)
-   Contact

#### Follow us

[Minax on LinkedIn ](https://www.linkedin.com/in/thomas-bidaultt)[Minax on Discord ](https://discord.gg/NG4nR3E5a6)[Minax on YouTube ](https://www.youtube.com/@MrFrosasi)[Minax on Instagram ](https://www.instagram.com/ccsglobal.inc)

© 2026 Minax. All rights reserved. 

[Privacy policy](/privacy)[Terms of service](/terms)[Terms](/cgv)[Cookies](/cookies)[Legal notices](/legal)Manage cookies

Home Discover Create Projects Profile 

Nous utilisons **Google Analytics**, **Microsoft Clarity** et **Reddit Pixel** pour analyser l'utilisation du site et améliorer votre expérience. [En savoir plus](/cookies)

Tout refuserTout accepter