Skip to content

Nothing enforces the Gradle daemon JVM, so a pre-25 JDK fails with a cryptic KSP class-file error #246

Description

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions