Files
javademos/docs/15-build-files.md

62 lines
4.6 KiB
Markdown

# 15. Build files: what the 21 to 25 upgrade does to Maven, Gradle and Lombok
Prev: [14. The 21 to 25 lane](14-lanes-21-to-25.md) · [Back to the README](../README.md)
The build half of the [upgrade guide](https://ankurm.com/java-21-to-25-upgrade-guide-ai-prompts/), run by `scripts/upgrade.sh`.
Everything below is a run on JDK 21, 23, 24, 25, 26 or 27; the transcripts are in `output/92` to `output/97`.
## 15.1 Maven: `maven.compiler.release`
[`lanes/upgrade/pom.xml`](../lanes/upgrade/pom.xml) sets `maven.compiler.source`, `target` and `release` to 25.
* Building on **JDK 21** fails at once: `error: release version 25 not supported`. The build JDK has to be 25 before the release can be 25 ([92](output/92-maven-release.txt)).
* Building on **JDK 25** succeeds and the class file has major version 69. Building on **JDK 27** with `release` still 25 also succeeds and also produces version 69: a newer JDK can compile for 25. Running that class on JDK 21 gives `UnsupportedClassVersionError` (65 is Java 21).
* `--release N` restricts the compiler to the *API of release N*, not just the language level and byte-code version. [94](output/94-release-flag.txt): JDK 27's `javac --release 25` rejects `String.equalsFoldCase` (new in 26).
`javac -source 25 -target 25` compiles it with a warning, and the class then dies on a JDK 25 with `NoSuchMethodError`. That, and not "rejecting internal APIs", is what `--release` buys you.
## 15.2 Gradle: the toolchain block, and the JDK that runs Gradle
[`lanes/upgrade/build.gradle.kts`](../lanes/upgrade/build.gradle.kts) asks for a Java 25 toolchain. Two different JDKs are involved ([93](output/93-gradle-toolchain.txt)):
* the toolchain JDK, which compiles your code: Gradle 8.14.3 found and used JDK 25 for this while itself running on JDK 21;
* the JDK that runs Gradle itself: Gradle 8.14.3 on JDK 25 fails (`25.0.4.1` with a Kotlin DSL script; `Unsupported class file major version 69` with a Groovy one, seen in a side run), Gradle 9.1.0 on JDK 25 works.
Gradle's [compatibility table](https://docs.gradle.org/current/userguide/compatibility.html), read 2026-09-21: Java 25 needs Gradle 9.1.0, Java 26 needs 9.4.0, and Java 27 is not listed for any release yet.
## 15.3 Lombok and annotation processing
[`lanes/src/LombokProbe.java`](../lanes/src/LombokProbe.java) needs Lombok's processor to compile. [95](output/95-lombok-matrix.txt):
| Lombok | JDK 25 | JDK 26 | JDK 27 |
|---|---|---|---|
| 1.18.34 | fails: `ExceptionInInitializerError` | fails | fails |
| 1.18.40 | compiles | compiles | fails |
| 1.18.46 | compiles | compiles | fails |
| 1.18.48 | compiles | compiles | compiles |
The probe uses one annotation, so "compiles" is a lower bound: the [Lombok changelog](https://projectlombok.org/changelog) is the support statement (JDK 25 in 1.18.40, JDK 26 in 1.18.46, JDK 27 in 1.18.48).
1.18.40 compiling on 26 does not contradict it.
Separate trap, same file: since JDK 23 `javac` no longer runs annotation processors it merely finds on the class path. JDK 21 compiles the probe with a `Note: Annotation processing is enabled...`; JDK 23, 24 and 25 give `cannot find symbol` for `getName()`.
Maven and Gradle projects that declare the processor with `annotationProcessorPaths` / `annotationProcessor` are unaffected; hand-run `javac` lines and IDE setups that relied on discovery are not.
## 15.4 Mockito and ByteBuddy
[`lanes/src/MockitoProbe.java`](../lanes/src/MockitoProbe.java) mocks an interface and stubs one call. [97](output/97-mockito-bytebuddy.txt), each Mockito with the ByteBuddy it declares:
| Mockito | ByteBuddy | JDK 21 | JDK 25 | JDK 27 |
|---|---|---|---|---|
| 5.12.0 | 1.14.15 | works | fails | fails |
| 5.17.0 | 1.15.11 | works | fails | fails |
| 5.18.0 | 1.17.5 | works | works | works |
| 5.23.0 | 1.17.7 | works | works | works |
The failure is `Mockito cannot mock this class` (ByteBuddy cannot instrument class files newer than it knows). 5.18.0 was the first version that worked; 5.14.2, 5.16.1 and 5.17.0 failed in a side run (not committed).
Even on the working versions the JVM prints `WARNING: A Java agent has been loaded dynamically` on 21, 25 and 27, and Mockito 5.23.0 adds a note that self-attaching will stop working: the fix is to add Mockito as an agent, as its documentation describes.
## 15.5 `jdeps --jdk-internals`
[96](output/96-jdeps-internals.txt) is `jdeps --jdk-internals` on the class from [`UnsafeWarning.java`](../lanes/src/UnsafeWarning.java): it names `sun.misc.Unsafe` before the JVM prints its run-time warning ([86](output/86-unsafe-and-jni.txt)).
Prev: [14. The 21 to 25 lane](14-lanes-21-to-25.md) · [Back to the README](../README.md)