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.
# 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.0Common 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.
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17' # identical in compile and run jobsConstrain the compile target to the runtime
Set --release to the runtime version so bytecode stays loadable.
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
--releaseto the runtime JDK.