Java Class File Major & Minor Version

Every Java developer at some point comes across the following Exception and wonders what the strange numbers mean.

Exception in thread "main" java.lang.UnsupportedClassVersionError: java\SampleClass : Unsupported major.minor version 51.0

This happens when the class file has been compiled for a newer Java version than the version of the runtime, that is trying to load the class. But why the heck is it such a strange version number like 51.0 and what version of Java is this?

In the third part of the series about the Java Class File Format I take a look at the minor and major version information.

Java Versions vs Java Class File Versions

The versioning of the Java class files is somehow strange. The first version of Java wasn’t actually Java, but Oak. Oak was the name the language was supposed to be called. However, the first stable version of Java was JDK 1.0.2, which was released in 1996 as Java 1.

The first version of Java class files is major version 45 and minor version 3 and are valid for JDK 1 & 1.1. I don’t know why versions start with 45 and a short research on Google gave me no answer. However, we have to accept this. For class files for Java 1.2 and later the minor version is always 0, and the major version is incremented for each Java version.

In the table below you can see a mapping from the Java Class File major version to the actual Java JDK version.

Major Version Minor Version Java version
45 3 1(.0.2)
45 3 1.1
46 0 1.2
47 0 1.3
48 0 1.4
49 0 5
50 0 6
51 0 7
52 0 8
53 0 9

Since Java is backwards compatible, the version of the class file has to be lower or equal than the version of the runtime. That means that e.g. for Java 7 you can use class files of version 51 or smaller.

The version of the current JDK 9 is 53. You can find a little history of features of the class file attributes in the JVM specification.

Compiling Java Sources For an Older Version of the JVM

When you get an UnsupportedClassVersionError, you know the version of the class you want to use and your runtime version do not match. Some class files may contain byte codes that are not known to older Java versions, some source files may use syntax constructs like lambda expressions, some may be just compiled with a newer Java compiler version than the runtime version and a class may be exactly the same but the major version byte.

No matter what the reason is, if the major version is bigger than the version of your runtime, the class does not load. Therefore you may want to compile Java sources for an older runtime. You can do this simply ba specifying the -target and -source version flags of the compiler:

> javac -target 1.2 -source 1.2 java\SampleClass.java
warning: [options] bootstrap class path not set in conjunction with -source 1.2
warning: [options] source value 1.2 is obsolete and will be removed in a future release
warning: [options] target value 1.2 is obsolete and will be removed in a future release
warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
4 warnings

Of course this only works when no features of later Java versions are used in the source file. Otherwise compilation will fail:

> javac -target 1.7 -source 1.7 java\SampleClass.java
warning: [options] bootstrap class path not set in conjunction with -source 1.7
java\SampleClass.java:4: error: lambda expressions are not supported in -source 1.7
 new Thread(()->System.out.println("Hello, World!")).start();
 ^
 (use -source 8 or higher to enable lambda expressions)
1 error
1 warning

Also compiling from a specific source version to a lower target version won’t work:

> javac -target 1.7 -source 1.8 java\SampleClass.java
javac: source release 1.8 requires target release 1.8

Gradle & Maven

Reading the Minor And Major Version From the Class File

For the ClassFileReader utility we need to read the version information from the InputStream. According to the JVM Specification the two bytes after the magic number contain the minor version, the following two bytes contain the major version.

Supported Versions

For the version I created an enum to represent the different Java versions the ClassFileReader can handle:

public enum Version {
    JAVA_1_2(46, 0),
    JAVA_1_3(47, 0),
    JAVA_1_4(48, 0),
    JAVA_5(49, 0),
    JAVA_6(50, 0),
    JAVA_7(51, 0),
    JAVA_8(52, 0),
    JAVA_9(53, 0);

    public final int major;
    public final int minor;

    Version(int major, int minor) {
        this.major = major;
        this.minor = minor;
    }

    public static Version of(int major, int minor) {
        for (Version v : Version.values()) {
            if (v.major == major && v.minor == minor) {
                return v;
            }
        }
        throw new IllegalArgumentException("No matching Java Class File version " +
                "for given major/minor version found: " + major + "/" + minor);
    }
}

The Test

As a test data I just compiled an empty class for different target versions using the -target javac parameter as described above.

@RunWith(Parameterized.class)
public class ReadVersionsTest {

    @Parameters(name = "Class File Version {0}")
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][]{
                {"1_2", Version.JAVA_1_2},
                {"1_3", Version.JAVA_1_3},
                {"1_4", Version.JAVA_1_4},
                {"5", Version.JAVA_5},
                {"6", Version.JAVA_6},
                {"7", Version.JAVA_7},
                {"8", Version.JAVA_8},
                {"9", Version.JAVA_9}
        });
    }

    @Parameter(0)
    public String versionSuffix;

    @Parameter(1)
    public Version version;

    @Test
    public void testReadVersion1_2() throws IOException {
        // GIVEN
        InputStream in = ReadVersionsTest.class.getResourceAsStream(
                "/versions/SampleClass" + versionSuffix + ".class");

        // WHEN
        ClassFile classFile = ClassFile.read(in);

        //THEN
        assertThat(classFile.getVersion()).isEqualTo(version);
    }
}

The Test is a JUnit parameterized test that runs the same test (just reading the class and checking the version) for different parameters (our supported Java versions).

You can find the complete source code for this blog post tagged on the GitHub repo.

Summary

  • The Java version is encoded in a Java class file. The major version differs from the actual Java version, the minor version is 0 for every relevant Java version.
  • If you want to use classes on an older JVM, you may have to compile them for the correct target version.

Sources

Author: Thomas Lemmé

I am a Java Developer currently working at one of the top APM vendors as a Java agent developer.

2 thoughts on “Java Class File Major & Minor Version”

Leave a Reply

Your email address will not be published. Required fields are marked *