The build requires JDK 25, but nothing enforces the Gradle daemon's JVM, so any contributor whose java on PATH predates 25 gets a cryptic class-file-version error from an unrelated module instead of a clear "wrong JDK" message.
What happens
On a machine where the Gradle daemon runs JDK 21, ./gradlew build fails in :e2e-tests-micronaut:kspKotlin:
io/micronaut/kotlin/processing/.../TypeElementSymbolProcessorProvider has been compiled by a
more recent version of the Java Runtime (class file version 69.0), this version of the Java
Runtime only recognizes class file versions up to 65.0
Class file 69 = Java 25, 65 = Java 21. Micronaut 5.x baselines on Java 25 — verified by reading the bytecode header of TypeElementSymbolProcessorProvider.class in micronaut-inject-kotlin-5.1.12.jar (CA FE BA BE 00 00 00 45, 0x45 = 69).
Nothing about that message says "your JDK is too old", and it surfaces in a framework e2e module rather than anywhere the contributor was working.
Why the toolchain doesn't prevent it
buildSrc/src/main/kotlin/com/pkware/gradle/JavaConventionsPlugin.kt:26-30 declares the toolchain, and it is the only JVM-version declaration in the repo:
configure<JavaPluginExtension> {
toolchain { languageVersion.set(JavaLanguageVersion.of(25)) }
}
That governs compilation and test JVMs. It does not govern the daemon. KSP2 is the default at the KSP version in use (com.google.devtools.ksp:2.3.11, no ksp.useKSP2 override), and it loads symbol processors inside the Kotlin compiler process rather than forking to the toolchain JVM — so the processor jar is loaded by the daemon's JDK, not the declared 25.
There is no gradle/gradle-daemon-jvm.properties and no org.gradle.java.home in gradle.properties, so the daemon inherits whatever java is on PATH.
Not a regression
CI is fine — .github/workflows/ci.yml sets java-version: 25 and ./gradlew build passes on main. And the Micronaut version is not the trigger: git log -S"micronaut = " -- gradle/libs.versions.toml shows the key was introduced in b0d1404, and every Renovate bump since (5.1.8 → 5.1.12) was already Java-25-targeted, since the major-5 baseline predates them all. The requirement has simply always been implicit.
Suggested fix
Add gradle/gradle-daemon-jvm.properties with toolchainVersion=25 so Gradle provisions or selects the right daemon JVM itself and fails with an actionable message otherwise. Worth stating the JDK 25 requirement in the README's prerequisites too — CLAUDE.md currently says "JDK 17+", which is now wrong.
Low priority: the workaround is one line (export JAVA_HOME=$(/usr/libexec/java_home -v 25)). It costs a new contributor an afternoon of misdirected debugging in an unrelated module, though.
The build requires JDK 25, but nothing enforces the Gradle daemon's JVM, so any contributor whose
javaon PATH predates 25 gets a cryptic class-file-version error from an unrelated module instead of a clear "wrong JDK" message.What happens
On a machine where the Gradle daemon runs JDK 21,
./gradlew buildfails in:e2e-tests-micronaut:kspKotlin:Class file 69 = Java 25, 65 = Java 21. Micronaut 5.x baselines on Java 25 — verified by reading the bytecode header of
TypeElementSymbolProcessorProvider.classinmicronaut-inject-kotlin-5.1.12.jar(CA FE BA BE 00 00 00 45, 0x45 = 69).Nothing about that message says "your JDK is too old", and it surfaces in a framework e2e module rather than anywhere the contributor was working.
Why the toolchain doesn't prevent it
buildSrc/src/main/kotlin/com/pkware/gradle/JavaConventionsPlugin.kt:26-30declares the toolchain, and it is the only JVM-version declaration in the repo:That governs compilation and test JVMs. It does not govern the daemon. KSP2 is the default at the KSP version in use (
com.google.devtools.ksp:2.3.11, noksp.useKSP2override), and it loads symbol processors inside the Kotlin compiler process rather than forking to the toolchain JVM — so the processor jar is loaded by the daemon's JDK, not the declared 25.There is no
gradle/gradle-daemon-jvm.propertiesand noorg.gradle.java.homeingradle.properties, so the daemon inherits whateverjavais on PATH.Not a regression
CI is fine —
.github/workflows/ci.ymlsetsjava-version: 25and./gradlew buildpasses onmain. And the Micronaut version is not the trigger:git log -S"micronaut = " -- gradle/libs.versions.tomlshows the key was introduced inb0d1404, and every Renovate bump since (5.1.8 → 5.1.12) was already Java-25-targeted, since the major-5 baseline predates them all. The requirement has simply always been implicit.Suggested fix
Add
gradle/gradle-daemon-jvm.propertieswithtoolchainVersion=25so Gradle provisions or selects the right daemon JVM itself and fails with an actionable message otherwise. Worth stating the JDK 25 requirement in the README's prerequisites too —CLAUDE.mdcurrently says "JDK 17+", which is now wrong.Low priority: the workaround is one line (
export JAVA_HOME=$(/usr/libexec/java_home -v 25)). It costs a new contributor an afternoon of misdirected debugging in an unrelated module, though.