Skip to content
Latchkey

Java Bytecode Target Newer Than Runtime - Fix Version Skew in CI

The compile stage produced bytecode for a newer Java release than the JDK used to run it. The two pipeline stages use different JDKs, so the runtime cannot load classes built for a version it does not understand.

What this error means

Compilation succeeds, but a later run/test stage throws UnsupportedClassVersionError because the runtime JDK is older than the compile target - a classic compile-vs-run JDK skew.

java runtime
# compile stage: JDK 21 -> bytecode major 65
# run stage: JDK 17 (major 61)
java.lang.UnsupportedClassVersionError: com/example/App has been compiled by a
more recent version of the Java Runtime (class file version 65.0), this version
of the Java Runtime only recognizes class file versions up to 61.0

Common causes

Different JDKs across pipeline stages

A build job uses JDK 21 to compile while a deploy/test job uses JDK 17 to run. The newer bytecode is unreadable by the older runtime.

Compile target not constrained

Without a --release matching the runtime, the compiler emits bytecode for the (newer) compile JDK, exceeding what the runtime supports.

How to fix it

Pin the same JDK across stages

Use one JDK version for both compile and run jobs.

.github/workflows/ci.yml
- uses: actions/setup-java@v4
  with:
    distribution: temurin
    java-version: '17'   # identical in compile and run jobs

Constrain the compile target to the runtime

Set --release to the runtime version so bytecode stays loadable.

Terminal
mvn -B -Dmaven.compiler.release=17 clean package
# Gradle: tasks.withType<JavaCompile> { options.release.set(17) }

How to prevent it

  • Pin one JDK version across all pipeline stages, or constrain the compile --release to the runtime JDK.

Related guides

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