Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Introduction

One of the new features that Java 9 brings us is the capability to build Multi-Release JARs (MRJAR). As the JDK Enhancement Proposal says, this allows us to have different Java release-specific versions of a class in the same JAR.

In this tutorial, we explore how to configure an MRJAR file using Maven.

2. Maven

Maven is one of the most used build tools in the Java ecosystem; one of its capabilities is packaging a project into a JAR.

In the following sections, we'll explore how to use it to build an MRJAR instead.

3. Sample Project

Let's start with a basic example.

First, we'll define a class that prints the Java version currently used; before Java 9, one of the approaches that we could use was the System.getProperty method:

public class DefaultVersion {
    public String version() {
        return System.getProperty("java.version");
    }
}

Now, from Java 9 and onward, we can use the new version method from the Runtime class:

public class DefaultVersion {
    public String version() {
        return Runtime.version().toString();
    }
}

With this method, we can get a Runtime.Version class instance that gives us information about the JVM used in the new version-string scheme format.

Plus, let's add an App class to log the version:

public class App {

    private static final Logger logger = LoggerFactory.getLogger(App.class);

    public static void main(String[] args) {
        logger.info(String.format("Running on %s", new DefaultVersion().version()));
    }

}

Finally, let's place each version of DefaultVersion into its own src/main directory structure:

├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── baeldung
│   │   │           └── multireleaseapp
│   │   │               ├── DefaultVersion.java
│   │   │               └── App.java
│   │   └── java9
│   │       └── com
│   │           └── baeldung
│   │               └── multireleaseapp
│   │                   └── DefaultVersion.java

4. Configuration

To configure the MRJAR from the classes above, we need to use two Maven plugins: the Compiler Plugin and the JAR Plugin.

4.1. Maven Compiler Plugin

In the Maven Compiler Plugin, we need to configure one execution for each Java version we'll package.

In this case, we add two:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <executions>
                <execution>
                    <id>compile-java-8</id>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </execution>
                <execution>
                    <id>compile-java-9</id>
                    <phase>compile</phase>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                    <configuration>
                        <release>9</release>
                        <compileSourceRoots>
                            <compileSourceRoot>${project.basedir}/src/main/java9</compileSourceRoot>
                        </compileSourceRoots>
                        <outputDirectory>${project.build.outputDirectory}/META-INF/versions/9</outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

We'll use the first execution compile-java-8 to compile our Java 8 class and the compile-java-9 execution to compile our Java 9 class.

We can see that it's necessary to configure the compileSourceRoot and outputDirectory tags with the respective folders for the Java 9 version.

However, as of maven-compiler-plugin 3.7.1, we don't need to set the output directory manually. Instead, all we have to do is to enable the multiReleaseOutput property:

<configuration> 
    <release>9</release> 
    <compileSourceRoots> 
        <compileSourceRoot>${project.basedir}/src/main/java9</compileSourceRoot> 
    </compileSourceRoots> 
    <multiReleaseOutput>true</multiReleaseOutput>
</configuration>

When set to true, the compiler plugin moves all release-specific classes to the META-INF/versions/${release} directory. Please note that we have to set the release tag to the desired Java version here, otherwise the compiler plugin fails.

4.2. Maven JAR Plugin

We use the JAR plugin to set the Multi-Release entry to true in our MANIFEST file. With this configuration, the Java runtime will look inside the META-INF/versions folder of our JAR file for version-specific classes; otherwise, only the base classes are used.

Let's add the maven-jar-plugin configuration:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>3.2.0</version>
    <configuration>
        <archive>
            <manifestEntries>
                <Multi-Release>true</Multi-Release>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

5. Testing

It's time to test our generated JAR file.

When we execute with Java 8, we'll see the following output:

[main] INFO com.baeldung.multireleaseapp.App - Running on 1.8.0_252

But if we execute with Java 14, we'll see:

[main] INFO com.baeldung.multireleaseapp.App - Running on 14.0.1+7

As we can see, now it's using the new output format. Note that although our MRJAR was built with Java 9, it's compatible with multiple major Java platform versions.

6. Conclusion

In this brief tutorial, we saw how to configure the Maven build tool to generate a simple MRJAR.

As always, the full code presented in this tutorial is available over on GitHub.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Juan M.
3 months ago

Hi Guys, 
 
Note that since version 3.8.0 of the maven-compiler-plugin supports a multiReleaseOutput option which can compile classes directly to the appropriate META-INF/versions/{release} directory.
 
So, instead of:
 
<outputDirectory>${project.build.outputDirectory}/META-INF/versions/9</outputDirectory>
 
We can write:
 
<multiReleaseOutput>true</multiReleaseOutput>
 

Loredana Crusoveanu
3 months ago
Reply to  Juan M.

Hi Juan,
Thanks for the feedback!
We’ll take a look into this and update the article.

Darío Gemi
Darío Gemi
3 months ago

Thanks! It is very clear and well explained!!!

Comments are closed on this article!