A compiled Java .class file starts with a fixed header (0xCAFEBABE), followed by a pair of numbers: minor_version and major_version. The pair (commonly written as major.minor, e.g., 52.0) identifies which Java platform level the bytecode targets. The JVM uses this to decide whether it can load the class. If the class was compiled for a newer platform than the JVM supports, you’ll get UnsupportedClassVersionError.
Why It Matters:
- Backward compatibility: Newer JVMs can generally run older class files, but not the other way around.
- Build reproducibility: Ensuring all modules target the same release avoids subtle runtime issues.
- Tooling alignment: IDEs, build tools, containers, and CI images must agree on the target level to prevent version skew.
Quick mapping highlights:
- Java 8 →
52.0 - Java 11 →
55.0 - Java 17 (LTS) →
61.0 - Java 21 (LTS) →
65.0 - Java 22 →
66.0, 23 →67.0, 24 →68.0, 25 →69.0, 26 →70.0, 27 →71.0, 28 →72.0
| JDK Version | Class File Format Version |
|---|---|
| 1.0 | 45.0 |
| 1.1 | 45.3 |
| 1.2 | 46.0 |
| 1.3 | 47.0 |
| 1.4 | 48.0 |
| 5 | 49.0 |
| 6 | 50.0 |
| 7 | 51.0 |
| 8 | 52.0 |
| 9 | 53.0 |
| 10 | 54.0 |
| 11 | 55.0 |
| 12 | 56.0 |
| 13 | 57.0 |
| 14 | 58.0 |
| 15 | 59.0 |
| 16 | 60.0 |
| 17 | 61.0 |
| 18 | 62.0 |
| 19 | 63.0 |
| 20 | 64.0 |
| 21 | 65.0 |
| 22 | 66.0 |
| 23 | 67.0 |
| 24 | 68.0 |
| 25 | 69.0 |
| 26 | 70.0 |
| 27 | 71.0 |
| 28 | 72.0 |
Note:
- Early JDK branding used
1.x(e.g.,1.5,1.6) but these correspond to modern names5,6, etc. The table above reflects the modern naming for 5+. - There was no official
1.9brand; Java 9 is simply9 → 53.0(already shown above).
How to check a class file’s version
- Using
javap(JDK tool):javap -v path/to/Some.class | find "major"Look for a line like
major version: NN(e.g.,52for Java 8). For modern compilers,minoris typically0. -
Reading the header directly (forensics style):
- Confirm magic bytes:
CA FE BA BE. - Next 2 bytes:
minor_version. - Next 2 bytes:
major_version(e.g.,0x003D=61→ Java 17).
- Confirm magic bytes:
How to compile for a specific Java level
- Recommended (single flag):
javac --release 21 -d out $(find src -name "*.java")--releaseconsistently sets language features, APIs, and the class file version. -
Legacy approach (not preferred, can mismatch APIs):
javac -source 1.8 -target 1.8 -bootclasspath "%JAVA8_HOME%\\jre\\lib\\rt.jar" -extdirs "" - Maven (maven-compiler-plugin):
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <release>21</release> </configuration> </plugin> - Gradle (Groovy DSL):
java { toolchain { languageVersion = JavaLanguageVersion.of(21) } } // Or explicitly set the target bytecode tasks.withType(JavaCompile).configureEach { options.release = 21 }
Common failure and how to fix
- Symptom:
java.lang.UnsupportedClassVersionError: … has been compiled by a more recent version of the Java Runtime.
- Causes:
- Running on an older JRE/JDK than the class file requires.
- Mixed toolchains or inconsistent
--release/target levels in a multi-module build.
- Fixes:
- Upgrade the runtime to meet the class file’s
major.minorlevel; or - Recompile with an older target using
--release <level>that matches your deployment runtime; and - Standardize toolchains via Maven/Gradle toolchains and CI images to avoid skew.
- Upgrade the runtime to meet the class file’s
Tips and caveats
- Prefer
--releaseover-source/-targetbecause it also validates against platform APIs for that release. - Preview features do not change the class file version; they require
--enable-previewat compile and run time, but the mapping still follows the JDK’s version. - When publishing libraries, choose the lowest
--releasethat matches your supported runtime matrix to maximize compatibility; consider multi-release JARs if you need newer APIs while keeping a baseline.
