Files
Iris/docs/85 - Maintainer - MC Version Bump.md
T
Brian Neumann-Fopiano 365205ad0a d
2026-08-12 13:52:16 -04:00

16 KiB

85 - Maintainer — Minecraft Version Bump

gradle.properties minecraftVersion is the single source of truth for the target Minecraft version. Most build outputs derive from it. This document lists every edit required to move Iris to a new Minecraft version, in order.

How to run the bump

Use a dedicated branch or worktree and begin from a green build. Record the old and new Minecraft, Bukkit API, loader, datapack-format, and NMS binding values before editing. Complete the ordered steps without skipping ahead, run the focused check after each platform boundary, then run the full all-platform build and the disposable-server lifecycle runbooks.

Stop when an upstream API, mapping, or loader artifact is unavailable; do not hide the gap behind a legacy fallback. The bump is complete only when generated artifact names, metadata ranges, data fixers, NMS selection, tests, and documentation all agree on the same target.

Source of truth

gradle.properties:

  • minecraftVersion — target MC version (e.g. 26.2). Drives BuildConstants.MINECRAFT_VERSION, the com.mojang:minecraft coordinate, all mod-metadata minecraft ranges, and the three loader jar names. The CraftBukkit jar name comes from bukkitMinecraftRange instead (step 1).
  • apiVersion — Bukkit plugin api-version (e.g. 26.1). Deliberately decoupled from minecraftVersion: it is the lowest Minecraft release line the single plugin artifact loads on (currently 26.1 so one jar serves 26.1.2 and 26.2). Bump it only when dropping support for the older line.
  • fabricLoaderVersion — Fabric Loader version.
  • forgeVersion — Forge version (<mc>-<forge>).
  • neoForgeVersion — NeoForge version.
  • irisVersion — bump the trailing -<mc> suffix to match (e.g. 4.0.0-26.2 -> 4.0.0-27.0).

Ordered steps

  1. Edit gradle.properties: update minecraftVersion, fabricLoaderVersion, forgeVersion, neoForgeVersion, and the irisVersion suffix. Revisit apiVersion only if the bump drops support for the oldest Minecraft line the plugin artifact still loads on. bukkitMinecraftRange is deliberately absent from gradle.properties; it defaults to '26.1.2-26.2' in build.gradle and is the version string the CraftBukkit jar filename carries, so edit that default (or pass -PbukkitMinecraftRange=) whenever the supported Bukkit range moves.

  2. Edit gradle/libs.versions.toml:

    • paper-api — the compile pin for :core and :adapters:bukkit:plugin (currently 26.1.2.build.74-stable). Like apiVersion, it is held at the lowest supported release line so one plugin jar loads across the whole range; raise it only when dropping that line.
    • spigot — the Spigot API pin (<mc>-R0.1-SNAPSHOT). Used compileOnly by the three modded adapters and :probe only, never by the Bukkit plugin. Bump it with minecraftVersion.
    • fabricApi-* — the ten Fabric API module versions, if the new MC requires different Fabric API builds. Each module is versioned independently (<version>+<build-hash>). The ten are base, registrySync, resourceLoader, lifecycleEvents, commandApi, eventsInteraction, networking, rendering, keyMapping, permission. Every one of them is bundled jar-in-jar and must be declared in fabric.mod.json jars — see step 7.
  3. Edit core/src/main/java/art/arcane/iris/core/nms/datapack/DataVersion.java (manual, structural):

    • Append a new enum constant named for the MC version with dots replaced by underscores: V<mc>("<mc>", <packFormat>, <DataFixer>::new). The current tail is V26_1_2("26.1.2", 101, DataFixerV1217::new), V26_2("26.2", 107, DataFixerV1217::new).
    • packFormat comes from https://minecraft.wiki/w/Pack_format.
    • getLatest() returns the last enum constant, so append at the end; do not reorder, and leave UNSUPPORTED first.
    • Reuse the existing fixer when the datapack format did not change (26.1.2 and 26.2 both use DataFixerV1217). If it did change, add a new IDataFixer implementation in a versioned subpackage under core/src/main/java/art/arcane/iris/core/nms/datapack/ (existing ones are v1192, v1206, v1213, v1217).
  4. Register and select the new Bukkit NMS binding. The revision tag is spelled out by hand in five places plus the sources; miss any one and the build fails or the plugin refuses to bind at boot.

    • settings.gradle — add include(':adapters:bukkit:nms:v<major>_<minor>_R<rev>').
    • build.gradle — add the binding to the nmsBindings map: v<major>_<minor>_R<rev>: '<paper-dev-bundle-version>'. That value is passed to paperweight's paperDevBundle(...), not to Spigot BuildTools; it is currently '26.1.2.build.74-stable', held at the same low pin as paper-api.
    • build.gradle — update art/arcane/iris/core/nms/v26_2_R1/NMSBinding.class in requiredBukkitArtifactEntries to the new tag, or verifyBukkitArtifact fails against the shipped jar.
    • .github/workflows/ci.yml — update the hardcoded :adapters:bukkit:nms:v26_2_R1:test task path in the verify job.
    • core/src/main/java/art/arcane/iris/core/nms/NmsBindingSelector.java — the runtime half, fully hand-maintained. Update SUPPORTED_TAG, SUPPORTED_VERSIONS, and the isSameRelease(...) guard. INMS.bindExact reflects on art.arcane.iris.core.nms.<tag>.NMSBinding, so an unedited selector rejects the new server before the new binding is ever loaded. Update core/src/test/java/art/arcane/iris/core/nms/NmsBindingSelectorTest.java in the same edit; it asserts the tag and the accepted/rejected versions.
    • Create the binding sources under adapters/bukkit/nms/v<major>_<minor>_R<rev>/src/main/java/art/arcane/iris/core/nms/v<major>_<minor>_R<rev>/, matching the existing set: NMSBinding, IrisChunkGenerator, CustomBiomeSource, VanillaStructureBiomes, DatapackStructureStateFilter, ImportedFeatureStage.
  5. Update loader version-range metadata (manual floors/ranges only; the minecraft ranges are templated from minecraftVersion and need no edit):

    • adapters/fabric/src/main/resources/fabric.mod.jsonminecraft is ~${minecraftVersion} (auto). Update the fabricloader floor (currently >=0.19.3) if the loader minimum changes, and the jars list if the bundled Fabric API modules change.
    • adapters/forge/src/main/resources/META-INF/mods.tomlminecraft versionRange is [${minecraftVersion}] (auto). Update loaderVersion (currently [65,66)) and the forge dependency versionRange (also [65,66)). Both are hand-maintained and both are upper-bounded, so both must move on every Forge line bump.
    • adapters/neoforge/src/main/resources/META-INF/neoforge.mods.tomlminecraft versionRange is [${minecraftVersion}] (auto). loaderVersion (currently [3,)) is the javafml specification version, not the NeoForge version, and rarely moves. The neoforge dependency versionRange is hardcoded (currently [26.2,26.3)) and is not templated from minecraftVersion — hand-edit it on every bump or the mod will refuse to load on the new NeoForge line.
  6. Re-verify the mapping-coupled files. Six files name Mojang-mapped classes, fields, and method descriptors directly. Nothing templates them, nothing fails fast at build time if a name moved, and a stale entry surfaces as a silent no-op or a load-time crash. Check every one against the new MC jar.

    Access widener (Fabric) — accessWidener v2 official, so the names are Mojang-mapped:

    • adapters/fabric/src/main/resources/irisworldgen.accesswidener

      • MinecraftServer.levels Ljava/util/Map;
      • MinecraftServer.executor Ljava/util/concurrent/Executor;
      • MinecraftServer.storageSource Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;
      • PackRepository.sources Ljava/util/Set; (accessible and mutable)

      Verify: each field still exists with that exact descriptor. Loom fails the build on an unresolvable AW entry, so a rename shows up as an AW error — read it, do not delete the line.

    Access transformers (Forge and NeoForge) — must stay in sync with each other and with the AW:

    • adapters/forge/src/main/resources/META-INF/accesstransformer.cfg

    • adapters/neoforge/src/main/resources/META-INF/accesstransformer.cfg

      • both: public net.minecraft.server.MinecraftServer levels / executor / storageSource

      Verify: the three ATs match the first three AW entries. Note the ATs have no PackRepository entry — Forge/NeoForge reach the pack sources through their own hooks, so do not add one without a reason. Wired via minecraft { accessTransformer.from(...) } in adapters/forge/build.gradle and neoForge { accessTransformers.from(...) } in adapters/neoforge/build.gradle; NeoForge additionally declares the file in an [[accessTransformers]] block in neoforge.mods.toml.

    Mixin configs — three JSONs, eight mixin classes, all targeting Mojang-mapped members. The JSONs only list class names; the mapped method names and descriptors below live in the .java mixin classes, so check those files, not the configs:

    • adapters/fabric/src/main/resources/irisworldgen.mixins.json (package art.arcane.iris.fabric.mixin, compatibilityLevel JAVA_21, Fabric only)

      • BlockItemMixin -> BlockItem.placeBlock, @At("RETURN")
      • BlockMixin -> Block.getDrops(...) with a full descriptor (BlockState, ServerLevel, BlockPos, BlockEntity, Entity, ItemInstance) — the highest-churn entry in the repo; the parameter list changes across MC versions
      • PackRepositoryMixin -> PackRepository.<init>, @At("RETURN")
    • adapters/modded-common/src/main/resources/irisworldgen.entity.mixins.json (package art.arcane.iris.modded.mixin, compatibilityLevel JAVA_21, all three loaders)

      • EntityPersistenceMixin -> Entity.shouldBeSaved
      • LivingEntityLootMixin -> LivingEntity.dropFromLootTable(ServerLevel, DamageSource, boolean) — full descriptor
      • MobAwarenessMixin -> Mob.serverAiStep, injecting at a field target (Lnet/minecraft/world/entity/Mob;noActionTime:I) — verify the field, not just the method
    • adapters/modded-common/src/main/resources/irisworldgen.client.mixins.json (package art.arcane.iris.client.mixin, compatibilityLevel JAVA_21, classes listed under client rather than mixins)

      • IrisWorldOpenFlowsMixin -> WorldOpenFlows.confirmWorldCreation and WorldOpenFlows.openWorldCheckWorldStemCompatibility
      • IrisWorldTypeEntryMixin -> WorldCreationUiState.WorldTypeEntry.describePreset, plus a @Shadow member — shadows break silently if the field is renamed

      The client mixin config lives in modded-common/src/main/resources but the classes live in adapters/client-common/src/main/java/art/arcane/iris/client/mixin/; the modded mixin classes live in adapters/modded-common/src/main/java/art/arcane/iris/modded/mixin/. All three adapters add both shared source dirs, so one edit hits every loader.

      Registration differs per loader and each place must list the same configs:

      • Fabric — fabric.mod.json mixins (all three; the client one gated on "environment": "client").
      • NeoForge — [[mixins]] blocks in neoforge.mods.toml (entity + client).
      • Forge — no toml entry; FML 26.2 reads neither [[mixins]] nor accessTransformers from a Forge toml. The jar manifest attribute MixinConfigs in adapters/forge/build.gradle is the only shipping registration, and the minecraft { runs { … } } block passes --mixin.config separately (client gets entity + client, server gets entity only). Adding a mixin config on Forge means editing the manifest attribute and the relevant run args.

      injectors.defaultRequire is 1 in all three configs, so a mixin that no longer applies fails the run instead of degrading quietly. Treat any "mixin apply failed" line as a bump blocker, and run both runClient and runServer per loader — client-only mixins are not exercised by a server run.

  7. Reconcile the Fabric jar-in-jar list across three places that must agree exactly. adapters/fabric/build.gradle adds every Fabric API module to the jij configuration, and shadowJar copies it into META-INF/jars, renaming each artifact through the nestedFabricApiJars map (an explicit module-to-filename table, not a version-stripping regex — a bundled jar with no map entry fails the build with Undeclared Fabric API nested jar). The jij configuration is transitive = false, so the bundled set is exactly the declared set. Adding or removing a module means editing the jij dependency list, nestedFabricApiJars, and the jars array in fabric.mod.json together. After changing the module list, confirm the jar agrees:

    unzip -l "dist/Iris v<version> [Fabric] <mc>+<loader>.jar" | grep META-INF/jars
    

    An entry in jars with no matching nested jar makes the loader refuse the mod; a nested jar missing from jars is dead weight the loader never mounts.

  8. Build and verify, in this order:

    • ./gradlew :core:check — includes bukkitPurityRatchet and the NMS selector tests. Green before touching the platform builds.
    • ./gradlew buildBukkit — runs verifyBukkitArtifact first, then drops the jar in dist/.
    • ./gradlew buildFabric
    • ./gradlew buildForge
    • ./gradlew buildNeoforge

    Each build* task runs its artifact verifier as a dependency, so a missing resource, an unapplied mixin config, or a new Bukkit-coupled class fails the task rather than shipping. ./build-all.sh runs all four serialized with local VolmLib substitution off; use it for the final pass. Good looks like: four jars in dist/ carrying the new version strings, and a Verified … line for every platform.

  9. After a successful bump, re-run the operator runbooks and GoldenHash parity on all platforms (31 - Operator Runbooks.md, 32 - Determinism & Goldenhash.md) and continue with 86 - Maintainer - Release Checklist.md only after 87 - Maintainer - Release Readiness.md allows GO or GO-WARN.

Derived automatically (do not hand-edit on a version bump)

  • Bukkit plugin api-versionadapters/bukkit/plugin/build.gradle expands apiVersion into both plugin.yml and paper-plugin.yml at processResources.
  • BuildConstants.MINECRAFT_VERSION — stamped by the generateTemplates task in core/build.gradle from minecraftVersion (template at core/src/main/templates/BuildConstants.java); consumed by Tasks.supportedVersions.
  • Mod-metadata minecraft version ranges — templated from minecraftVersion at processResources.
  • The three loader jar names and the com.mojang:minecraft coordinate — composed from minecraftVersion in the build scripts. The CraftBukkit jar name is the exception: it carries bukkitMinecraftRange, not minecraftVersion (step 1).

Notes

  • build.gradle, the adapter build.gradle files, and settings.gradle carry defensive .getOrElse(...) defaults for the version properties. gradle.properties always overrides them, so a bump does not require touching those fallbacks; refresh them only if the checked-in default should track the current release. bukkitMinecraftRange is the one exception — it has no gradle.properties entry, so its '26.1.2-26.2' default is the live value.
  • The Java literals "26.2" and "26.1.2" intentionally remain in DataVersion.java (structural enum constants), core/src/test/java/art/arcane/iris/core/nms/MinecraftVersionTest.java, and core/src/test/java/art/arcane/iris/core/lifecycle/PaperLibBootstrapTest.java. The test files use MC version strings as parser fixtures, not as a version source; update them only when the version string formats they exercise change.
  • NmsBindingSelector.java and NmsBindingSelectorTest.java also hardcode MC version literals, but those are not intentional leftovers — they are required edits (step 4).