Skip to content
Latchkey

Wrong JAVA_HOME / JDK Version in CI - Pin the Toolchain

Your build is running on a different JDK than you think. JAVA_HOME or PATH resolves an interpreter you did not intend, so compiles, tests, or tools behave unexpectedly.

What this error means

Code that builds locally fails in CI with a version-specific error, or java -version / mvn -version prints a JDK other than what you configured. Subtle issues (different default GC, API availability) trace back to the wrong JDK.

CI log
$ java -version
openjdk version "11.0.22" 2024-01-16
# but the project expects 21 - JAVA_HOME points at the wrong JDK

Common causes

Image default JDK, not the one you set

The runner ships several JDKs. Without explicitly setting JAVA_HOME/PATH, the build inherits the image default, which may not match your project.

setup-java skipped or shadowed

If actions/setup-java did not run, or a later step changed PATH/JAVA_HOME, the JDK in use is not the one you configured.

How to fix it

Pin the JDK and assert it

Set up the exact JDK and verify it before building.

.github/workflows/ci.yml
- uses: actions/setup-java@v4
  with:
    distribution: temurin
    java-version: '21'
- run: |
    java -version
    echo "JAVA_HOME=$JAVA_HOME"
    mvn -B -version

Use build-tool toolchains for the compile JDK

Decouple the compile JDK from the ambient JAVA_HOME with Maven/Gradle toolchains so the build is reproducible regardless of the runner default.

build.gradle.kts
// Gradle build.gradle.kts
java {
    toolchain { languageVersion = JavaLanguageVersion.of(21) }
}

How to prevent it

  • Always pin the JDK in CI; never rely on the image default.
  • Add a java -version / mvn -version assertion step early.
  • Use Gradle/Maven toolchains so the compile JDK is explicit.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →