Checkpoint Lab — Java Toolchains, Compiler Configuration, Annotation Processing, Kotlin/JVM, and Cross-Version Builds
Create a JVM compatibility dossier that independently proves Gradle runtime, compiler JDK, emitted bytecode target, annotation-processing boundary, and test-runtime matrix cells.
Learning objectives
- Produce a compatibility dossier that independently records Gradle runtime JVM, compiler toolchain, bytecode target, processor path, and test runtime cells.
- Predict matrix outcomes before changing the build and compare predictions with independent evidence.
- Introduce and diagnose one runtime/target mismatch without requiring Kotlin or a second installed JDK.
- Run two runtime cells when available, or explicitly simulate/document an unavailable cell without converting it into a false pass.
- Restore the baseline and verify cleanup/rollback.
1. Checkpoint scenario and acceptance contract
You are preparing a small library for consumers that require Java 17+. The CI controller currently runs Gradle 9.7.1 on JDK 21. Your dossier must prove five independent claims:
- which JVM runs Gradle;
- which JDK supplies
javac; - which Java release the artifact targets;
- that annotation processing is compile-time only and deterministic enough for this fixture;
- which runtime matrix cells were actually executed versus simulated/unavailable.
A “BUILD SUCCESSFUL” line alone satisfies none of those claims.
2. Setup and exact baseline
Reuse the Lesson 2 project, or recreate it from these baseline
files. The mandatory assumptions are Gradle 9.7.1 Wrapper, JDK 21
available locally, Java 21 toolchain for compilation, Java 17
release target, local :processor, and JUnit 6.1.3. Keep
Gradle state inside the lab.
cd gradle-toolchain-lab
export GRADLE_USER_HOME="$PWD/.gradle-user-home"
./gradlew --version
./gradlew -q javaToolchains
./gradlew projects
Copy ./gradlew --version and
javaToolchains output into a plain-text dossier such as
evidence/runtime-and-toolchains.txt. The evidence
directory is lab output; whether a real repository commits such
reports is a separate team policy.
3. Predict the graph and artifact before running
| Prediction | Expected baseline | How to verify |
|---|---|---|
| Gradle runtime | JDK 21 | ./gradlew --version. |
| Compiler toolchain | Java 21 JDK |
javaToolchains +
:library:compileJava --info.
|
| Artifact bytecode | Java 17 / major 61 | javap -verbose. |
| Generated source |
BuildInfo.java exists under
build/generated
|
Filesystem inspection after compilation. |
| Processor runtime leakage | None |
Compare annotationProcessor and
runtimeClasspath.
|
| JDK 21 cell | Pass | testOn21 report. |
| JDK 17 cell | Pass if installed; otherwise unavailable |
javaToolchains plus testOn17 or
documented simulation.
|
Write down at least the bytecode and matrix predictions before executing the tasks. This prevents post-hoc explanations from replacing a testable model.
4. Capture the clean baseline
mkdir -p evidence
./gradlew -g "$PWD/.gradle-user-home" --version | tee evidence/gradle-version.txt
./gradlew -g "$PWD/.gradle-user-home" -q javaToolchains | tee evidence/java-toolchains.txt
./gradlew -g "$PWD/.gradle-user-home" clean :library:testOn21 --info | tee evidence/test-on-21.log
./gradlew -g "$PWD/.gradle-user-home" :library:dependencies --configuration annotationProcessor > evidence/annotation-processor-graph.txt
./gradlew -g "$PWD/.gradle-user-home" :library:dependencies --configuration runtimeClasspath > evidence/runtime-graph.txt
javap -verbose library/build/classes/java/main/dev/academy/library/CompatibilityLibrary.class | grep 'major version' | tee evidence/class-major.txt
test -f library/build/generated/sources/annotationProcessor/java/main/dev/academy/generated/BuildInfo.java
Expected class major is 61. The JDK 21 test report should be present
under the custom test task’s result/report directories. The
processor graph should contain :processor; the runtime
graph should not contain it as an ordinary implementation/runtime
dependency.
5. Run or simulate the JDK 17 cell honestly
# First inspect whether Java 17 exists.
grep -n 'Language Version: 17' evidence/java-toolchains.txt || true
# If present, execute and preserve the real cell.
./gradlew -g "$PWD/.gradle-user-home" :library:testOn17 --info | tee evidence/test-on-17.log
If JDK 17 exists, require a passing report. If it does not, preserve
the “no matching toolchain” failure (or skip invoking after
preflight) and create
evidence/test-on-17-NOT-RUN.txt containing: requested
JDK 17, reason unavailable, no resolver configured, expected Java 17
class-file ceiling major 61, and a statement that bytecode
inspection is only simulation evidence—not an executed runtime cell.
6. Inject a target/runtime mismatch
Change only options.release in
library/build.gradle.kts from 17 to 21. Do not change
the Gradle runtime or processor dependency graph.
tasks.withType<JavaCompile>().configureEach {
// Deliberate checkpoint failure: breaks Java 17 runtime compatibility.
options.release.set(21)
}
./gradlew -g "$PWD/.gradle-user-home" clean :library:compileJava
javap -verbose library/build/classes/java/main/dev/academy/library/CompatibilityLibrary.class | grep 'major version' | tee evidence/broken-class-major.txt
# Execute the real old-runtime failure only when JDK 17 exists.
./gradlew -g "$PWD/.gradle-user-home" :library:testOn17 --stacktrace | tee evidence/broken-test-on-17.log
The independent bytecode evidence should change from major 61 to major 65. With an actual JDK 17 launcher, the runtime cell should fail with an unsupported class-version error before the test can prove application behavior. Without JDK 17, major 65 plus the documented Java 17 ceiling is the required simulated mismatch evidence.
7. Repair the narrowest state and re-verify
Restore options.release.set(17). Do not change
JAVA_HOME, delete global caches, or remove the
processor. Then rebuild from controlled state.
./gradlew -g "$PWD/.gradle-user-home" clean :library:testOn21
javap -verbose library/build/classes/java/main/dev/academy/library/CompatibilityLibrary.class | grep 'major version' | tee evidence/repaired-class-major.txt
./gradlew -g "$PWD/.gradle-user-home" :library:dependencies --configuration runtimeClasspath > evidence/repaired-runtime-graph.txt
The repaired major version must return to 61. If JDK 17 is
available, re-run testOn17 and require success. The
runtime graph should remain free of the processor implementation
before and after the mismatch because target correction and
dependency isolation are independent controls.
8. Optional Kotlin/JVM mismatch exercise
If Kotlin JVM 2.4.10 is already resolved and you chose to add the
optional Kotlin lane, introduce a second, language-level mismatch by
setting Kotlin JvmTarget to 21 while Java targets 17.
Preserve the target-validation failure, then restore
JvmTarget 17. Do not change
kotlin.jvm.target.validation.mode to hide the failure.
9. Complete the migration/compatibility dossier
| Evidence item | Baseline | Broken state | Repaired state |
|---|---|---|---|
| Gradle runtime JVM | JDK 21 | unchanged | unchanged |
| Compiler toolchain | JDK 21 | unchanged | unchanged |
| Java release / major | 17 / 61 | 21 / 65 | 17 / 61 |
| Processor path | :processor present |
unchanged | unchanged |
| Runtime graph | processor absent | unchanged | unchanged |
| JDK 21 cell | pass | may pass with Java 21 bytecode | pass |
| JDK 17 cell | pass if installed / otherwise NOT RUN | fail if installed; simulated incompatible if unavailable | pass if installed / otherwise NOT RUN |
This table demonstrates why a newer-runtime pass is insufficient. The broken Java 21 bytecode can still pass on JDK 21, while the minimum-runtime claim is invalid.
10. Final verification checklist
- Gradle runtime and detected JDKs are recorded.
-
compileJavatoolchain request is explicit and inspectable. - Final
javapevidence is major 61. -
Generated
BuildInfo.javais produced underbuild/generated, not committed source. -
:processoris onannotationProcessorand absent from ordinary runtime dependencies. - JDK 21 runtime test passes.
- JDK 17 cell is either actually executed and passes or explicitly marked NOT RUN with reason; no false pass.
- All deliberate changes are restored.
11. Cleanup and rollback
# Verify the final source-controlled baseline first.
grep -n 'options.release.set(17)' library/build.gradle.kts
cd ..
rm -rf gradle-toolchain-lab
Only the disposable lab is removed. No normal Gradle User Home, system JDK, IDE configuration, CI image, or global init script is modified.
12. What Chapter 23 adds to a production build-engineering model
The operating model now has explicit identities for the build runtime, compile toolchain, artifact target, compiler extensions, and supported execution runtimes. That makes JVM upgrades and CI image changes reviewable migrations instead of workstation folklore.
Chapter 24 builds on this foundation by asking when the same declared inputs may legitimately reuse prior work through incremental builds, the configuration cache, build cache, remote cache, and reproducibility controls.
Knowledge check
In the broken checkpoint state, why can
testOn21 still pass while Java 17 compatibility is
broken?
Java 21 can execute Java 21 bytecode; the minimum supported runtime is the cell that exposes the compatibility failure.
What independent evidence proves the target changed even if JDK 17 is unavailable?
javap -verbose changes from class-file major 61 to
65. That is simulation evidence of incompatibility, not an
executed JDK17 test.
Which configuration should contain the local processor implementation?
annotationProcessor; the annotation API can be
compileOnly when it is source-only.
What must a dossier say about an unavailable JDK 17 cell?
Explicitly NOT RUN/unavailable, why it was unavailable, whether provisioning was disabled/unconfigured, and what was simulated. Never mark it passed.
What is the least destructive repair for the deliberate release mismatch?
Restore options.release = 17, rebuild in the
isolated state, verify major 61, and rerun supported runtime
cells.
What does Chapter 24 add next?
Reuse/reproducibility state: incremental work, configuration cache, build cache, remote cache, and proof that cached outputs remain correct.
Official references and version notes
- Gradle Compatibility Matrix — Gradle 9.7.1 currently requires JVM 17–26 to run; supported toolchain compile/test versions are a separate concern.
-
Toolchains for JVM projects
— Java toolchain selection,
--release, vendor selection, discovery, provisioning, andjavaToolchainsdiagnostics. - JavaToolchainSpec API — valid toolchain specifications and language-version/vendor semantics.
-
Gradle Java Plugin
—
annotationProcessor, annotation processor path isolation, generated sources, incremental annotation processing, and Java compilation behavior. -
Building Java & JVM Projects
— current guidance for toolchains,
release, and legacy source/target compatibility. - Kotlin Gradle project configuration — Kotlin/JVM plugin 2.4.10 examples, JVM toolchain behavior, and Java/Kotlin JVM-target compatibility checks.
-
Kotlin compiler options
— typed
compilerOptions,JvmTarget, and the deprecation of legacykotlinOptions. - Kotlin releases — Kotlin 2.4.10 is the current stable line used only in the optional Kotlin/JVM lane.
Version-sensitive behavior was rechecked against current Gradle and
Kotlin primary documentation on 2026-08-24. Mandatory labs use
Gradle 9.7.1 through the previously verified Wrapper, JDK 21 as the
Gradle runtime and Java compiler toolchain, Java 17 as the strict
--release target, JUnit 6.1.3 for the Java test
fixture, and an isolated GRADLE_USER_HOME. Kotlin/JVM
2.4.10 is optional because applying it may require plugin
resolution. Toolchain auto-provisioning is not assumed: an
unavailable JDK cell is recorded/simulated unless the learner has
deliberately configured a reviewed resolver.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.