Java9 series (viii) Multi-Release JAR Files

  java9

Order

This article mainly studiesJEP 238: Multi-Release JAR Files

multi-release jar (MR JAR)

Java9 newly supports the functions of multi-release jar, including jar, javac, javap, jdeps and other commands. The so-called multi-release jar can include multiple jdk versions. At runtime, the JVM loads a class that conforms to the version according to the current environment. This enables jar packages to try the features of the new JDK as early as possible while being compatible with the old version.

Specify the compiled version through the –release parameter, depending onJEP 247: Compile for Older Platform VersionsTo compile into a class specifying the JDK version

The specific change is that a new attribute has been added to the MANIFEST.MF file in the META-INF directory:

Multi-Release: true

Then a versions directory is added under the META-INF directory. If java9 is to be supported, there is a directory of 9 under the versions directory.

Example

java8

  • com.example.lang
public class StackHelper {

    public static String getCurrentStack() {
        System.out.println("java 8 stack");
        return Arrays.stream(Thread.currentThread()
                .getStackTrace())
                .map(element -> element.toString())
                .collect(Collectors.joining("\n"));
    }
}
  • maven
    <groupId>com.example</groupId>
    <artifactId>mr-jar-java</artifactId>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>mr-jar-java9</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifestEntries>
                            <Specification-Title>${project.artifactId}</Specification-Title>
                            <Implementation-Title>${project.artifactId}</Implementation-Title>
                            <Implementation-Version>${project.version}</Implementation-Version>
                            <Multi-Release>true</Multi-Release>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>add-java9-classes</id>
                        <phase>generate-resources</phase>
                        <goals>
                            <goal>unpack-dependencies</goal>
                        </goals>
                        <configuration>
                            <includeGroupIds>com.example</includeGroupIds>
                            <includeArtifactIds>mr-jar-java9</includeArtifactIds>
                            <excludeTransitives>true</excludeTransitives>
                            <outputDirectory>${project.build.directory}/generated-resources/META-INF/versions/9</outputDirectory>
                            <includes>**/*.class</includes>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <version>3.0.0</version>
                <executions>
                    <execution>
                        <id>add-resource</id>
                        <phase>generate-resources</phase>
                        <goals>
                            <goal>add-resource</goal>
                        </goals>
                        <configuration>
                            <resources>
                                <resource>
                                    <directory>${project.build.directory}/generated-resources/</directory>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

Note that jar packages of java9 are relied on here, and then Multi-Release is set to true. classes of java9 are put into the META-INF/versions/9 directory through compilation and packaging. The original classes of java8 will remain unchanged.

java9

  • com.example.lang
public class StackHelper {

    public static String getCurrentStack() {
        System.out.println("java 9 stack");
        return StackWalker.getInstance()
                .walk(frames -> frames.map(Object::toString)
                        .collect(joining("\n")));
    }
}
  • module-info.java
module mr.jar.java9 {
    exports com.example.lang;
}
  • maven
    <groupId>com.example</groupId>
    <artifactId>mr-jar-java9</artifactId>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.target>9</maven.compiler.target>
        <maven.compiler.source>9</maven.compiler.source>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.2</version>
                <configuration>
                    <release>9</release>
                </configuration>
            </plugin>
        </plugins>
    </build>

pack

mvn clean install
  • The contents of the jar package are as follows
➜  mr-jar-java-0.0.1-SNAPSHOT git:(master) ✗ tree
.
├── META-INF
│   ├── MANIFEST.MF
│   ├── maven
│   │   └── com.example
│   │       └── mr-jar-java
│   │           ├── pom.properties
│   │           └── pom.xml
│   └── versions
│       └── 9
│           ├── com
│           │   └── example
│           │       └── lang
│           │           ├── StackHelper.class
│           │           └── StringHelper.class
│           └── module-info.class
└── com
    └── example
        └── lang
            ├── StackHelper.class
            └── StringHelper.class

12 directories, 8 files
  • Mf
Manifest-Version: 1.0
Created-By: Apache Maven 3.3.3
Built-By: demo
Build-Jdk: 9
Implementation-Title: mr-jar-java
Implementation-Version: 0.0.1-SNAPSHOT
Multi-Release: true
Specification-Title: mr-jar-java
  • Confirm java8 class version
➜  mr-jar-java-0.0.1-SNAPSHOT git:(master) ✗ javap -verbose ./com/example/lang/StackHelper.class
Classfile /Users/demo/multi-release-jar-demo/mr-jar-java8/target/mr-jar-java-0.0.1-SNAPSHOT/com/example/lang/StackHelper.class
  Last modified 2018年3月7日; size 1901 bytes
  MD5 checksum 9fafe51ca3df481e8c2264753c281a9a
  Compiled from "StackHelper.java"
public class com.example.lang.StackHelper
  minor version: 0
  major version: 52
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #15                         // com/example/lang/StackHelper
  super_class: #16                        // java/lang/Object
  interfaces: 0, fields: 0, methods: 3, attributes: 3

It can be seen that the major version is 52

  • Confirm java9 class version
➜  mr-jar-java-0.0.1-SNAPSHOT git:(master) ✗ javap -verbose ./META-INF/versions/9/com/example/lang/StackHelper.class
Classfile /Users/demo/multi-release-jar-demo/mr-jar-java8/target/mr-jar-java-0.0.1-SNAPSHOT/META-INF/versions/9/com/example/lang/StackHelper.class
  Last modified 2018年3月7日; size 1921 bytes
  MD5 checksum da6326681eb1b0584998a92178e22e27
  Compiled from "StackHelper.java"
public class com.example.lang.StackHelper
  minor version: 0
  major version: 53
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #14                         // com/example/lang/StackHelper
  super_class: #15                        // java/lang/Object
  interfaces: 0, fields: 0, methods: 3, attributes: 3

You can see that the major version is 53

run

  • java8
java 8 stack
java.lang.Thread.getStackTrace(Thread.java:1559)
com.example.lang.StackHelper.getCurrentStack(StackHelper.java:14)
Java8Main.main(Java8Main.java:15)
  • java9
java 9 stack
mr.jar.java9/com.example.lang.StackHelper.getCurrentStack(StackHelper.java:13)
mr.jar.java9.main/mr.jar.java9.main.Java9Main.main(Java9Main.java:17)

Note that java9 calls the project of multi-release jar, requires the module, and then adds module-path for compilation and running.

java --module-path ./target/classes:/Users/demo/.m2/repository/com/example/mr-jar-java/0.0.1-SNAPSHOT/mr-jar-java-0.0.1-SNAPSHOT.jar --module mr.jar.java9.main/mr.jar.java9.main.Java9Main

Jar command

The above example uses maven plug-in to package, or jar to package multi-release jar. The example is as follows:

javac --release 8 -d out/8 mr-jar-java8/src/main/java/com/example/lang/StackHelper.java
javac --release 9 -d out/9 mr-jar-java9/src/main/java/com/example/lang/StackHelper.java
jar -cfm out/mr-jar-demo.jar ./MANIFEST.MF -C out/8 .
jar -uf out/mr-jar-demo.jar --release 9 -C out/9 .

Here -c means creating jar package, -C means transferring to the directory to execute jar command without -C, -f specifies the file name of jar package, -m specifies manifest.mf file, -u adds file to jar package
Mf contains Multi-Release: true

Summary

The function of multi-release jar provided by java9 can enter multiple jdk versions in a jar package and support multi-release in java9 and above. Its advantages are, for example, the migration from java8 to java9. If jar on which java8 depends is itself multi-release, then upgrading to java9 is more convenient without changing maven dependency. The bad thing is that it smells of over-design. A jar contains multiple versions of class, which is somewhat redundant.

doc