Part-5: JMPS/Java Modules - multi-release JAR files

Let’s consider the scenario where are required compiled classes against the different Java SE versions: Java SE 8, Java SE 9… Java SE 15 and etc. Since the Java SE 9 it is possible to provide a multi-release JAR files. It means that inside the JAR file are available classes compiled agains different Java SE versions. A JAR file has a content root with classes and resources. It also has a META-INF directory with metadata (MANIFEST.FM file) about the versioning content that can be encoded in compatible way:

 
example structure:
wits-java-modules-main.jar
├── META-INF
│   ├── MANIFEST.MF
│   └── versions
│       └── 10
│           ├── com
│           │   └── modules
│           │       └── ModulesMain.class
│           └── module-info.class
├── com
│   └── modules
│       └── ModulesMain.class
└── module-info.class

MANIFEST.FM content:
Manifest-Version: 1.0
Created-By: 15 (Oracle Corporation)
Main-Class: com.modules.ModulesMain
Multi-Release: true 

Notice that the MANIFEST.FM file does contain the main attribute Multi-Release: true. To create such JAR file the class “ModulesMain.java” is required:

package com.modules;

import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ModulesMain {
    private static final Logger LOGGER = Logger.getLogger(ModulesMain.class.getName());
    public static void main(String[] args) {
        LOGGER.log(Level.INFO, "Welcome Java Modules!");
        LOGGER.log(Level.INFO, "Modular Stones: " + Arrays.asList("Application",
            "Libraries","JDK", "Module Aware Tools", "Language & JVM"));
        
        Runtime.Version version = Runtime.version();
        String message = " Java version: " + version;
        LOGGER.log(Level.INFO, message);
    }
} 

The file “ModulesMain.java” resides according to the package structure above. Let’s compile the source against a different java versions into different target directories. The compiled classes are prepared for the multi-version JAR file creation:

 
$java -version  
openjdk version "15" 2020-09-15
OpenJDK Runtime Environment (build 15+36-1562)
OpenJDK 64-Bit Server VM (build 15+36-1562, mixed mode, sharing)

//compile the content root against the Java SE 15
$javac -d ./target ./src/wits-main/module-info.java ./src/wits-main/com/modules/ModulesMain.java

//compile the content against the Java SE 10
$javac --release 10 -d ./target-10 ./src/wits-main/module-info.java ./src/wits-main/com/modules/ModulesMain.java

$jar --create --file wits-java-modules-main.jar --main-class com.modules.ModulesMain -C target . --release 10 -C target-10 .

The JAR file has been created and although all has been compiled by using version Java SE 15, the JAR file is possible to execute using version Java SE 10:

$java -version
output:
java version "10" 2018-03-20
Java(TM) SE Runtime Environment 18.3 (build 10+46)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10+46, mixed mode)

$java -jar wits-java-modules-main.jar
output:
Oct 08, 2020 12:04:44 AM com.modules.ModulesMain main
INFO: Welcome Java Modules!
Oct 08, 2020 12:04:44 AM com.modules.ModulesMain main
INFO: Modular Stones: [Application, Libraries, JDK, Module Aware Tools, Language & JVM]
Oct 08, 2020 12:04:44 AM com.modules.ModulesMain main
INFO:  Java version: 10+46 

When the JAR file does not contain a compatible classes the error message is displayed and code is not executed:

 
$java -version
output:
java version "9.0.4"
Java(TM) SE Runtime Environment (build 9.0.4+11)
Java HotSpot(TM) 64-Bit Server VM (build 9.0.4+11, mixed mode)

$java -jar wits-java-modules-main.jar
output:
Error: LinkageError occurred while loading main class com.modules.ModulesMain
        java.lang.UnsupportedClassVersionError: com/modules/ModulesMain has been compiled by a more recent version of the Java Runtime (class file version 59.0), this version of the Java Runtime only recognizes class file versions up to 53.0 
Main: Java tutorials