01
Capture the class name and version numbers
The error identifies a class that the JVM cannot read because its class-file version is unsupported. Record that class and the reported versions. It may be your entrypoint, but it may also be a dependency compiled for a newer Java release.
As a concrete example, Java 21 class files use major version 65, while Java 17 uses 61. A Java 17 runtime cannot load ordinary Java 21 bytecode. Those numbers describe the artifact, not a missing classpath entry. Adding the same JAR to the classpath again will not lower its class-file version.
02
Inspect the exact build and execution environments
Run version checks in the build job and where the artifact actually executes. A shell on the server may use a different Java installation from a system service. A Docker multi-stage build can compile with one JDK and run with a different base image.
For a class you control, javap exposes its class-file major version. When the failing class belongs to a dependency, inspect the exact dependency version in the packaged artifact. Do not assume your compiler target rewrites third-party classes.
java -version
javac -version
# Example for a class compiled into ./out:
javap -verbose -classpath out Hello | grep 'major version'
# In a deployed container, inspect that container's JVM:
docker exec example-java-api java -version03
Choose a compatibility direction deliberately
There are two basic paths: move execution to a runtime compatible with the artifact, or rebuild an application that is compatible with the required older runtime. The right choice depends on the deployment contract and dependency support, not only on which command removes the error fastest.
If the application intentionally uses newer language features or libraries requiring a newer JVM, targeting an older release is not a magic downgrade. Review those requirements before changing the build. Conversely, upgrading a server JVM should include the application’s normal compatibility and operational tests.
04
Use --release for a controlled older target
For an application intended to run on Java 17, a newer supported javac can compile suitable source with --release 17. The option targets the language, class-file level, and supported platform API for that release. Check the compiler’s supported release values; they are not unlimited.
This minimal example deliberately uses no newer APIs. Put it in Hello.java, compile it, and inspect the result. In Maven or Gradle, express the same target through the build tool’s compiler and toolchain configuration rather than a manual command outside the real build.
public class Hello {
public static void main(String[] args) {
System.out.println("compatible artifact");
}
}05
Inspect the result and test the actual packaged application
Compile the example using the intended target, then inspect its metadata. Major version 61 is the expected result for Java 17. Running the class on your current JDK only proves that current JVM can load it; it is not a substitute for testing on Java 17 when that is the deployment requirement.
For a real application, run the packaged JAR or image on the declared runtime and exercise important paths. Inspect dependencies as well. One incompatible transitive JAR can fail at startup or when a rarely used class is first loaded, even if your own classes target the correct release.
mkdir -p out
javac --release 17 -d out Hello.java
javap -verbose -classpath out Hello | grep 'major version'
java -cp out Hello06
Make the runtime contract visible in CI
Record compiler and execution runtime versions alongside the artifact identity. Keep build and runtime image choices explicit. A test that runs only inside the compiler image can miss the exact mismatch introduced by a separate runtime image.
When the fix is complete, preserve both sides of the proof: the class-file target of the built artifact and a successful execution on the intended runtime. A green compile stage alone is the condition that allowed this problem to escape in the first place.
Sınırları bil
The Java 21/17 version numbers are a specific example. Preview features, native libraries, third-party dependencies, and runtime behavior can impose additional compatibility requirements. --release does not transform dependency bytecode.
Kaynaklar ve ileri okuma
CoreTrailKit tarafından hazırlanan teknik yayın.
Bütün yazıları keşfet