Adding a Minecraft version
Add a protocol release, pin its runtime, add segments only where the code breaks, and cover the version with platform data and the live matrix.
A Minecraft version is supported when a protocol library release lists it, every segment that serves the release links against it, each platform that runs it has Java data, and Anvil's live matrix runs exactly the combinations the data calls verified. Work through the steps in order: each check shows what the next step needs. Pinning and live tests need network access.
Add or extend a release row
MCProtocolLib's releases are data in anvil-player/player-library/library-mcprotocol/mcprotocol-releases.toml.
One [[release]] row describes one MCProtocolLib build, which speaks one wire protocol:
| Field | Content |
|---|---|
version | MCProtocolLib release identifier; for a snapshot build, its unique timestamped version, such as 1.21.11-20260512.221357-18 |
module | The release's group:name:version coordinate, against which segments compile |
protocol | Wire protocol number |
minecraft | Every Minecraft version speaking that protocol; the highest is the release key |
verified | The versions the live matrix runs; nothing else |
java | Minimum Java feature version of the release's runtime, at least 8 |
features | ONLINE_AUTHENTICATION when its clients can log in to online-mode servers |
When the new Minecraft version speaks the protocol of an existing release and that build already
supports it, add the version to the release's minecraft list instead of adding a row. Each Minecraft
version belongs to exactly one release. Adding a version above a release's key changes the key; a
segment named after the old key must then be renamed, folder and package, because every segment
names a release key.
For a new protocol, add a row with one [[release.artifact]] table for the release module, its JAR
URL, and sha256 = "". The file keeps the latest patch of each Minecraft major. When the build is
served by a repository that settings.gradle.kts does not list yet, add it to both
libraryRepositories { url(...) } and dependencyResolutionManagement.
Pin the runtime closure
./gradlew :anvil-player:player-library:library-mcprotocol:pinLibraryReleasesFor each release, the task resolves module at runtime scope and rewrites that release's
[[release.artifact]] tables in class path order: each JAR's module, the URL of the first library
repository that serves the same bytes, and its SHA-256. It keeps the hand-written fields and comments
and never changes an existing pin; a JAR whose checksum differs from its pin fails the task before
anything is written. Review the rewritten closure before committing it.
The workers download exactly this closure. A release with an empty pin is listed but cannot be launched: preparation prints a warning, and creating a player that selects it is refused with the pin task as the remedy.
Let linkage decide about segments
Release-specific code lives only in segments: projects named V<major>_<minor>[_<patch>] after the
release key they start at, inside a side folder such as the client's mcprotocol-java. Built-in capabilities
have no segments: they speak the Java Edition protocol, which the client segments translate.
Each segment compiles against its own release and serves every later release up to the next segment.
After adding a release, check every segment against the locked closures:
./gradlew checkSegmentLinkageThe check task runs the same verification. A segment's report is
build/reports/segment/linkage.txt in its project.
-
Everything links: the new release reuses the newest segment of each side; no code changes.
-
The release is older than every segment of a side: no segment serves it. Add one named after its key that starts a chain of its own, as
V1_16_5does for the client and server packet names, or becomes the new start of the chain it shares packages with: move the chain's shared code into it and let the next segment extend it, replacing what changed, asV1_21_1extendsV1_20_6. -
A segment does not link: the failure names the missing or changed class, field, or method. Add a segment named after the new release key in that side only, such as
V26_1_2besideV1_21_11, with its classes in the package named after its folder in lower case, such as.v26_1_2. Let it extend the newest segment of the side and bring only the classes that differ or no longer link:segment { extends(projects.anvilPlayer.playerLibrary.libraryMcprotocol.mcprotocolJava.v12111) replaces("me.whereareiam.anvil.protocol.mcprotocol.client.v1_21_11.InteractionPackets") }The segment carries every class of its base chain but the bases' adapters and the classes it names in
replaces, which leave its JAR and its linkage manifest; the build fails when a replaced class is not a class of the base chain, or a class the segment still takes from its base uses it. Its adapter, aMcProtocolClientAdapterextending the side's client base, lists the release's translations: the base's ones that still link and its own replacements. Release-neutral code shared by a chain, such as the client base and the value translations, lives in the earliest segment of the chain, and a translation whose bulk does not change between releases keeps that bulk in an abstract base there, so a later segment overrides only the part that changed. Linkage tells which classes to replace: a base class that no longer links against the new release failscheckSegmentLinkageuntil the segment replaces it. Only when the release changes the library's packages or sessions throughout does the segment start a new chain without a base, asV1_18_2does. Every segment depends onmcprotocol-api, and a segment whose release's components are Adventure's (MCProtocolLib for Minecraft 1.16 on) also onmcprotocol-adventureforAdventureText; an older release flattens its own text components in its segment. The side'smodule-adapterconvention adds the new child automatically. -
The protocol cannot express the release: add or change the packet values in
protocol-java-apiand the area translations of every client segment, or changeMcProtocolClientinmcprotocol-apifor the client itself. An external capability with segments of its own changes its port in its API'spacketpackage and every segment implementing it.
Linkage does not cover what the game keeps in entity data. Each segment's EntityDataTable names, by entity
type, the index that carries each value the protocol models, and a release may move them while every class still
links: between 1.21.1 and 1.21.11 hanging entities gained their direction at index 8, which moved an item frame's
item from 8 to 9. Check
the table against the new release's entity data, and give the new release a segment of its own when an index moved.
A segment holds only port implementations and their META-INF/services/<port> descriptors; it never
wires a capability. Its folder must name a release key, and its classes must stay in its versioned
package, or the build fails.
Update the worker contracts
McProtocolWorkerContractTest starts a real worker for every release on its locked closure and
checks the protocol number, the selected client segment, and the installed built-in capabilities. Add
a row for the new release key, its protocol number, and the client segment that serves it, then run:
./gradlew :anvil-player:player-library:library-mcprotocol:mcprotocol-common:testClientSegmentLinkageTest in the same suite checks the built client segments against every release
they serve, as the worker does. Run ./gradlew check afterwards for the client segments' own tests and
the build conventions.
Add platform Java data
Each platform provider owns its version data:
| Platform | Version data |
|---|---|
| Paper | anvil-process/process-platform/platform-bukkit/paper-provider/src/main/resources/me/whereareiam/anvil/platform/paper/paper-versions.toml |
| Spigot | anvil-process/process-platform/platform-bukkit/spigot-provider/src/main/resources/me/whereareiam/anvil/platform/spigot/spigot-versions.toml |
| NeoForge | anvil-process/process-platform/platform-neoforge/neoforge-provider/src/main/resources/me/whereareiam/anvil/platform/neoforge/neoforge-versions.toml |
| Velocity | anvil-process/process-platform/platform-velocity/velocity-provider/src/main/resources/me/whereareiam/anvil/platform/velocity/velocity-versions.toml |
| BungeeCord | anvil-process/process-platform/platform-bungeecord/bungeecord-provider/src/main/resources/me/whereareiam/anvil/platform/bungeecord/bungeecord-versions.toml |
Add the version to known. Add a [[java]] row only when the platform's Java requirement changes at
that version: since, minimum, an optional maximum, the preferred LTS release, and, for a
platform that can run above its maximum, maximumBypassProperty. A row applies until the next one
starts. Processes run only on LTS releases (11, 17, 21, 25, then every fourth), so preferred must be
one of them; the [agent] minimumJava raises the effective minimum and default.
Then update the table in the Java guide.
JavaVersionTableDocumentationTest compares it with the providers' data and prints the expected rows
when they differ:
./gradlew :anvil-testkit:tests:runtime:test --tests '*JavaVersionTableDocumentationTest'Cover the version in the live matrix
VERIFIED means that Anvil's live matrix runs the combination. The matrix is the data file
anvil-testkit/tests/server/live-matrix.toml:
- A release key runs directly on Paper: add a
[[release]]row with the version and its pinned Paper build, in version order. The build registers itsreleaseTest-<minecraft>task, which runs the release suite against it, and CI gives it a job of its own. A version the IDE should list as its own scenario also gets aPaper<version>SystemScenariodefinition, such asPaper2612SystemScenario, that returnsCompatibilityScenarioFactory.releaseScenario(...). - A current version also runs on Spigot and behind Velocity and BungeeCord: add a
[[platform]]row naming the release and the Spigot JAR's SHA-256. - A version NeoForge supports also runs directly on NeoForge: add a
[[neoforge]]row with the version and its pinned NeoForge release.NeoForgeServerSystemTestruns it without the fixture plugin, which NeoForge cannot load.
Then list exactly the matrix's combinations as verified: the release's verified versions in
mcprotocol-releases.toml, and each platform's [verified] table with the Java versions the matrix
plans. CompatibilityScenarioFactoryTest plans the matrix without starting a server and fails when
the data and the matrix differ; every build runs it through the server suite's matrixTest task:
./gradlew :anvil-testkit:tests:server:matrixTestRun the release suite against the new version, then the platform suite when the version runs on the other platforms:
./gradlew :anvil-testkit:tests:server:releaseTest-1.21.11 -Panvil.testMode=full
./gradlew :anvil-testkit:tests:server:platformTest -Panvil.testMode=fullA capability the version adds or removes, such as dialogs in 1.21.6, is gated in its release test class with
TestedRelease.isAtLeast(...).
Replace 1.21.11 with the new version. Read Live tests for the other
suites and for retained diagnostics.
Update the documentation
Update the release and platform tables and the live matrix on Versions and compatibility, the Java guide's table, the version list in the README and the FAQ, and any capability guide whose behavior differs on the new version, such as the Messages examples. Validate the documentation as described in Documentation.