Understanding Java 9 Multi-Release Jar File with Example

[Updated: Nov 2, 2017, Created: Oct 31, 2017]

Java 9 enhanced the JAR file format which can contain multiple Java-release-specific versions of our class files to coexist in a single archive. Using this feature, we can upgrade our application/library to new version of Java without forcing the users to upgrade to the same Java version.

A Multi-Release JAR (MRJAR) declares following attribute in its MANIFEST.MF:

Multi-Release: true

The JAR then can include additional directories under META-INF/versions. For example:

jar root
  - A.class
  - B.class
  - META-INF
     - versions
        - 9
           - A.class

Say the jar root directory contains classes compiled in Java 8 (or earlier version). The directory META-INF/versions/9/ has new class A.class which has Java 9 features and is compiled in Java 9. If such a jar is loaded in Java 8 runtime, the root version of A.class will be used, whereas, if it is run in Java 9 then META-INF/version/9/A.class will be used.

Suppose later in the future Java 10 release, it is decided to upgrade A.class to take advantage of Java 10 features then the JAR will look like this:

jar root
  - A.class
  - B.class
  - META-INF
     - versions
        - 9
           - A.class
        - 10
           - A.class

Now above JAR can be loaded in three Java versions: 8, 9 and 10.

Generally speaking this feature allows the versions of a class designed for a later Java platform release to override the version of that same class designed for an earlier Java platform release.

The jar tool (and others) has also been enhanced to create multi-release JAR structure for us. Let's see an example to learn how to do that.

Example

Java 8 code

Following class is intended to run in Java 8:

package com.example;

import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

public class IOUtil {
  public static String convertToString(InputStream inputStream) throws IOException {
      System.out.println("IOUtil java 8 version");
      Scanner scanner = new Scanner(inputStream, "UTF-8");
      String str = scanner.useDelimiter("\\A").next();
      scanner.close();
      return str;
  }
}

Following is our main class:

package com.example;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Main {
  public static void main(String[] args) throws IOException {
          InputStream inputStream = new ByteArrayInputStream("test string".getBytes());
          String result = IOUtil.convertToString(inputStream);
          System.out.println(result);
      }
}

Java 9 code

After Java 9 release, it is decided to use new Java 9 features for the same API:

package com.example;

import java.io.IOException;
import java.io.InputStream;

public class IOUtil {
  public static String convertToString(InputStream inputStream) throws IOException {
      System.out.println("IOUtil java 9 version");
      try (inputStream) {
          String str = new String(inputStream.readAllBytes());
          return str;
      }
  }
}

As seen above, we used two new features of Java 9. The try-with-resource block with inputStream reference and the new InputStream.readAllBytes() method.

Let's now see how to create Multi-Release JAR (MRJAR) for our classes.

Compiling

Here is our directory structure.

D:\multi-release-jar-example>tree /A /F
+---my-lib-8
| \---src
| \---com
| \---example
| IOUtil.java
| Main.java
|
\---my-lib-9
\---src
\---com
\---example
IOUtil.java

We are going to compile our my-lib-8 classes in Java 8.

I have java 9 version on the system path:

D:\multi-release-jar-example>java -version
java version "9.0.1"
Java(TM) SE Runtime Environment (build 9.0.1+11)
Java HotSpot(TM) 64-Bit Server VM (build 9.0.1+11, mixed mode)

I also have JDK 1.8. Let's set it on the system path:

D:\multi-release-jar-example>set path=C:\jdk1.8.0_151\bin;%path%
D:\multi-release-jar-example>java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)

Now we are going to compile. First we have to create output folder for compiled classes as Java 8 does not create it automatically (but Java 9 does that):

D:\multi-release-jar-example\my-lib-8>mkdir out
D:\multi-release-jar-example\my-lib-8>javac -d out src/com/example/IOUtil.java src/com/example/Main.java

After compiling:

D:\multi-release-jar-example\my-lib-8>tree /A /F
+---out
| \---com
| \---example
| IOUtil.class
| Main.class
|
\---src
\---com
\---example
IOUtil.java
Main.java

Now we are going to compile our class under my-lib-9 folder using Java 9 compiler. Let's first set back Java 9 on the system path:

D:\multi-release-jar-example>set path=D:\Java9\jdk-9\bin;%path%
D:\multi-release-jar-example\my-lib-9>javac -d out src/com/example/IOUtil.java
D:\multi-release-jar-example>tree /A /F
+---my-lib-8
| +---out
| | \---com
| | \---example
| | IOUtil.class
| | Main.class
| |
| \---src
| \---com
| \---example
| IOUtil.java
| Main.java
|
\---my-lib-9
+---out
| \---com
| \---example
| IOUtil.class
|
\---src
\---com
\---example
IOUtil.java

Creating Multi-Release JAR

D:\multi-release-jar-example>jar --create --file my-lib.jar -C my-lib-8/out . --release 9 -C my-lib-9/out .

Note that we used new option --release VERSION. This option is responsible to create a multi-release JAR file. It places all files specified after the option into a versioned directory of the JAR file named META-INF/versions/VERSION/, where VERSION must be a positive integer whose value is 9 or greater.

Let's see what's in our newly created multi-release JAR file:

D:\multi-release-jar-example>jar --list --file my-lib.jar
META-INF/
META-INF/MANIFEST.MF
com/
com/example/
com/example/IOUtil.class
com/example/Main.class
META-INF/versions/9/
META-INF/versions/9/com/
META-INF/versions/9/com/example/
META-INF/versions/9/com/example/IOUtil.class

Let's extract it:

D:\multi-release-jar-example>mkdir my-lib-jar
D:\multi-release-jar-example\my-lib-jar>jar --extract --file ../my-lib.jar
D:\multi-release-jar-example\my-lib-jar>tree /A /F
+---com
| \---example
| IOUtil.class
| Main.class
|
\---META-INF
| MANIFEST.MF
|
\---versions
\---9
\---com
\---example
IOUtil.class

Let's see what's in MANIFEST.MF file:

D:\multi-release-jar-example\my-lib-jar/META-INF>type MANIFEST.MF
Manifest-Version: 1.0
Created-By: 9.0.1 (Oracle Corporation)
Multi-Release: true

Let's use javap to see where our two versions of IOUtil are placed by the jar tool (root and versions/9 locations):

D:\multi-release-jar-example\my-lib-jar>javap -c com.example.IOUtil | findstr invoke
1: invokespecial #1 // Method java/lang/Object."<init>":()V
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: invokespecial #7 // Method java/util/Scanner."<init>":(Ljava/io/InputStream;Ljava/lang/String;)V
22: invokevirtual #9 // Method java/util/Scanner.useDelimiter:(Ljava/lang/String;)Ljava/util/Scanner;
25: invokevirtual #10 // Method java/util/Scanner.next:()Ljava/lang/String;
30: invokevirtual #11 // Method java/util/Scanner.close:()V
D:\multi-release-jar-example\my-lib-jar/META-INF/versions/9>javap -c com.example.IOUtil | findstr invoke
1: invokespecial #1 // Method java/lang/Object."<init>":()V
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
17: invokevirtual #6 // Method java/io/InputStream.readAllBytes:()[B
20: invokespecial #7 // Method java/lang/String."<init>":([B)V
36: invokevirtual #8 // Method java/io/InputStream.close:()V
47: invokevirtual #10 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
54: invokevirtual #8 // Method java/io/InputStream.close:()V
76: invokevirtual #8 // Method java/io/InputStream.close:()V
87: invokevirtual #10 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
94: invokevirtual #8 // Method java/io/InputStream.close:()V

Everything looks good and is per our expectation.

Running the main class

Let's test our JAR in Java 9 first:

D:\multi-release-jar-example>java -cp my-lib.jar com.example.Main
IOUtil java 9 version
test string

Now run it in Java 8:

D:\multi-release-jar-example>C:\jdk1.8.0_151\bin\java -cp my-lib.jar com.example.Main
IOUtil java 8 version
test string

Example Project

Dependencies and Technologies Used :

  • JDK 1.8.0_151
  • JDK 9.0.1
Java 9 Multi-Release Jars Example Select All Download
  • multi-release-jar-example
    • my-lib-8
      • src
        • com
          • example
    • my-lib-9
      • src
        • com
          • example

See Also