This issue started with Minecraft plugins that use AI. I keep running into more of them, so I went to look at what they actually run on. That turned out to be a much bigger rabbit hole than I expected, and a surprisingly interesting one for a Java developer. So this time the whole issue is about Minecraft.
The game has also had a strange few months away from the code. In June McAfee described WeedHack, a campaign that spreads malware through fake mods and fake copies of mod websites, with more than 116,000 victims by its count. And on 11 October Poland’s Prime Minister Donald Tusk, at a meeting called after attacks in schools, named Minecraft as a game in which violence is becoming more visible; the education minister later explained that he meant radicalisation in chats and groups around games, not the game itself (it’s The Topic in Poland this week - and one that was an inspiration to me).
The code side is busy too. Minecraft: Java Edition 26.3 came out on 15 September, the first 26.4 snapshot made Vulkan the default renderer on 22 September, and on 26 September PaperMC said it plans to start signing its JARs.
A short orientation if you have never run a server. The multiplayer server is a separate JAR that you start with java -jar. Plugins run only on the server and use an API, so most of them never touch the game’s own classes. Mods change the game itself, often on both client and server, usually by rewriting Mojang’s bytecode.
1. Bukkit started as a hobby API and ended in a DMCA notice
The plugin side starts with Bukkit. Its first commit is from 21 December 2010, by Dinnerbone, and says “Let’s get this project started”.
The design had two layers. Bukkit was the API: interfaces for players, worlds, blocks, events and commands. CraftBukkit was a modified vanilla server that implemented them. Plugins targeted the Bukkit API, and CraftBukkit absorbed the changes in the game underneath. If you have written against a servlet container, the shape is familiar, and the first plugin most people write still looks like this fifteen years later:
public final class HelloPlugin extends JavaPlugin implements Listener {
@Override
public void onEnable() {
getServer().getPluginManager().registerEvents(this, this);
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
event.getPlayer().sendMessage(Component.text("Hello from the JVM"));
}
}Add a plugin.yml with the name, version and main class, drop the JAR into plugins/, restart.
In February 2012 the Bukkit team joined Mojang. On 21 August 2014 its lead, EvilSeph, announced the end of the project, citing the new EULA and “a half-applied license some could argue is invalid”. Jens Bergensten (jeb_) of Mojang answered the same day that the project “was bought by Mojang over two years ago, and isn’t his to discontinue.”
On 5 September 2014 a Bukkit contributor known as Wolvereness sent GitHub a DMCA notice. His code in Bukkit was GPL. CraftBukkit shipped it together with Mojang’s server code, which Mojang had never put under the GPL. So, he argued, the combined work could not be distributed legally. The notice named CraftBukkit, Spigot and several other forks.
Ask the GitHub API for Bukkit/CraftBukkit today and you still get HTTP 451, Unavailable For Legal Reasons, with “reason”:”dmca”. It is the rare HTTP 451 you can reproduce with curl.
Spigot found a way around it in November 2014. BuildTools is a small program that you run yourself. It downloads the vanilla server, decompiles it, applies the CraftBukkit and Spigot patches and compiles the result on your machine. Nobody distributes the combined JAR, because every server owner builds their own. The Spigot wiki calls it “our legal workaround to this problem”, and twelve years later it is still how you get a Spigot server.
2. Paper took over the platform, and in 2024 it stopped following Spigot
Paper started as a Spigot fork with extra performance fixes and API, rebased on Spigot for every release. On 13 December 2024 the team announced a hard fork. By then Paper carried “nearly 1600 additional patches with more than 130,000 lines of code over Spigot”, and from 1.21.4 on it would “no longer be bound to Spigot’s future changes”.
They could afford it. bStats, a metrics service that plugins embed, saw 188,837 Bukkit-family servers on 28 September 2026. Paper ran on 73.0% of them, Purpur (a Paper fork) on 9.5% and Spigot on 6.8%. The project that got the DMCA notice and wrote BuildTools is now the third option.
Around the hard fork, three things changed for plugin developers. Since 1.20.5 the runtime uses Mojang’s official names (section 4 explains those), and the versioned CraftBukkit package that plugins had to reach with reflection is gone. Server internals now come through a Gradle plugin, paperweight-userdev, because, as the documentation says, “redistributing the server JAR is against the Minecraft EULA”. Different paperwork than in 2014, same answer: you build it yourself, in Gradle this time. And from 26.1 on, versions dropped the -R0.1-SNAPSHOT suffix, which will please anyone who has explained to a Maven resolver why a release is called SNAPSHOT.
The old org.bukkit API still ships, and HelloPlugin above compiles against Paper 26.3. Paper is changing it in place, one release at a time. The 26.2 changelog says “MagmaCube no longer extends Slime”. Inheritance hierarchies do get fixed, it just takes a decade.
Then there is Folia. A vanilla server runs the world on one main thread, twenty ticks per second. Folia splits the world into regions, each with its own tick loop on a thread pool: “There is no main thread anymore”. Bukkit plugins assume one thread, so the README asks you to “have your expectations for compatibility at 0”. A plugin opts in with folia-supported: true and schedules work on the thread that owns the data:
@EventHandler
public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
// runs on whichever thread currently owns this player's region
player.getScheduler().run(this, task ->
player.sendMessage(Component.text("Welcome")), null);
}Folia runs on 0.67% of servers. It is still a good lesson in an API contract that never shows up in a method signature: which thread may touch the data.
3. Mods rewrite the game’s bytecode, and Mixin is how
Mods have no Bukkit. Fabric and NeoForge ship APIs, but when a mod needs to change the game’s own code, it uses Mixin, “a trait/mixin and bytecode weaving framework for Java using ASM”. ASM is the bytecode library inside many Java agents and mocking frameworks. Here is the example from the Fabric template mod for 26.3:
@Mixin(MinecraftServer.class)
public class ExampleMixin {
@Inject(at = @At("HEAD"), method = "loadLevel")
private void init(CallbackInfo info) {
// This code is injected into the start of MinecraftServer.loadLevel()V
}
}ExampleMixin is never instantiated. When the class loader loads MinecraftServer, Mixin reads both classes and “merges them together”. @At(”HEAD”) means the start of the method; other injection points target method calls, field reads or returns. If you have used an APM agent that instruments HttpServlet.service, it is the same idea, written by hand.
SpongePowered’s upstream Mixin has not had a release since July 2024. The active line is Fabric’s fork, net.fabricmc:sponge-mixin, and NeoForge depends on the same artifact. So the two rival mod loaders weave their bytecode with the same Fabric-maintained library.
NeoForge itself is a fork. Forge was the modding standard for most of the game’s history, until almost the whole team left in July 2023 and started NeoForge, because “many of the core assets are licensed by Lex’s company Forge LLC.” On Modrinth, one of the two big mod hosts, 46,401 mods are tagged for Fabric and 29,178 for NeoForge (tags overlap).
4. Mojang stopped obfuscating the game, and the mapping projects shut down
For most of its life Minecraft shipped obfuscated. A modder did not see MinecraftServer.loadLevel, they saw a meaningless class name and a one-letter method, different in every release. Crash logs looked the same.
The community answered with mappings, large tables that translate obfuscated names into readable ones. Fabric kept two: Yarn, with community-written names, and Intermediary, with names that stayed stable across versions. A whole layer of Gradle plugins, remappers and IDE support existed to move between these name sets. Mojang started publishing its own mappings in 2019, under a licence for “internal, reference purposes”.
On 29 October 2025 Mojang announced it would “remove obfuscation completely”, because “obfuscation makes modding harder”. Plus a bonus for anyone on call: “…crash logs will actually be readable!”
26.1, released on 24 March 2026, was the first release without obfuscation, so MinecraftServer and loadLevel from the mixin above are now also the names inside the shipped JAR. Two days after the announcement Fabric wrote that “Intermediary will no longer exist” and thanked “all 261 Yarn contributors for their 9 years, 2 months, and 17 days”. Quilt stopped its mappings too, and Paper dropped its remapper. The minimum price for mod authors was a rebuild: “no mods from 1.21.11 or before will work without, at a minimum, recompilation”, says Fabric’s 26.1 post.
The next break is bigger. Mojang is moving from OpenGL to Vulkan, with MoltenVK on macOS, and the 26.4 snapshot makes Vulkan the default. Mojang warned that this “will take modders more effort than the updates you undertake for each of our releases” and that OpenGL will be removed once Vulkan is stable. Mods that call OpenGL directly will have to migrate.
5. The largest Java 25 migration I have data for happened on game servers
On 28 September 2026 bStats saw 188,680 Bukkit-family servers that reported a Java version:
The table shows the five largest versions. Counting all of them, Java 25 or newer is at 64.6% and Java 21 or newer at 93.6%. Java 25 has been out for one year.
The numbers have a bias, because bStats only sees servers that run a bStats plugin. Even so, I do not know any other large population of JVM deployments where the current LTS is the majority one year after release.
The mechanism is visible in the release history. Minecraft moved to Java 16 in 2021, then to 17, to 21 in 1.20.5 and to 25 in 26.1. Each time the notes say the game “now requires” the new version and that the launcher ships “the Microsoft build of OpenJDK”, so players do not install Java themselves. Server software follows the game: Paper lists Java 25 as the minimum for every 26.x build. The platform owner raises the requirement, ships the runtime and makes the new version something users want. The migration business case fits in one line: the new release requires it.
The garbage collector followed. In 2018 Aikar published G1 flags for Minecraft servers, which Paper’s documentation still carries, with an explanation of the workload: “at least 800 Megabytes a second on a 30 player server”, “mostly short lived objects”. In 26.1 the client switched “from G1GC to ZGC for compatible devices” for “a more stable framerate”. In a game, a GC pause is a dropped frame.
Server guides are slower. Many still tell you to add -XX:+ZGenerational, which since JEP 490 in JDK 24 only prints an obsolete-option warning, on every start.
6. A plugin is a JAR from a stranger, and attackers know it
A plugin is ordinary Java code. It runs in the server’s JVM with the server’s permissions, and there is no sandbox.
Log4Shell did not even need a plugin. Minecraft logs with Log4j 2, and a server logs player chat, so in December 2021 Log4Shell needed only a chat message. The first public write-up listed Minecraft among the affected applications, and Mojang’s advisory gave JVM flags for each old version.
Then the JARs themselves turned hostile. In June 2023 the community found fractureiser in projects uploaded to CurseForge and BukkitDev. The infected JARs had a new static void method in their main class, called from the static initializer. It created a URLClassLoader pointing at a remote server and loaded the next stage. Later stages stole credentials and, with ASM, infected other JARs, including “entire Gradle and Maven caches“. Infected JARs in a developer’s local cache could then end up in whatever they built next.
That is the background for one line in the Paper 26.3 post: “we also plan on making produced JAR files more tamper-proof, e.g. by signing them.” It covers the server JAR, not your plugins, but it is a start.
7. Hytale is betting on Java plugins as well
Minecraft now has a newer competitor that made the same choice on purpose. Hytale, from Hypixel Studios, went into early access on 13 January 2026, and its modding strategy lists “Server Plugins - Java .jar files” as the most powerful kind of modification. There is no scripting language, and the reason will sound familiar to anyone who has maintained an embedded Lua or Groovy layer: “Programmers now have to juggle two languages, one of which usually feels “nerfed”.”
The server asks for Java 25 and ships “a pre-trained AOT cache (HytaleServer.aot) that improves boot times by skipping JIT warmup”:
java -XX:AOTCache=HytaleServer.aot -jar HytaleServer.jar --assets Assets.zipThat is Project Leyden‘s ahead-of-time cache, shipped by a game studio to its players. Most Leyden demos I have seen run Spring PetClinic. This one runs a game server.
PS: If you want to see a Minecraft server from the other end, look at Minestom, a library for writing your own server “without any code from Mojang”. You get the protocol and the building blocks and write the game yourself. Good weekend reading if you want a real networking and threading codebase in modern Java.
PS2: If this issue gave you ideas, Minecraft Development is an IntelliJ plugin with templates for Paper, Fabric and NeoForge that understands Mixin targets. And MCCoroutine lets a Kotlin @EventHandler be a suspend fun.
PS3: Our recurring series, “JVM Weekly in Strange Places,” continues! This time, we’re at TechCrunch Disrupt, where we’re showcasing Visdom by VirtusLab












