Number eight of the Java 9 series looks at JEP 238: Multi-Release JAR Files. The Java 9 articles look at some of the JDK Enhancement Proposals (JEPS) hoping to make their way into Java 9. Last week we looked at encapsulating most internal APIs. One of the work-arounds for libraries that use critical internal APIs is to use multi-release JAR files, allowing them to use multiple versions of Java in one JAR.
Multi-release JAR files
JEP 238 aims to extend the JAR file format. A JAR file has a content root (containing classes and resources) and a
META-INF directory. This contains metadata about the JAR. In order to allow multiple versions of class files to coexist, each specific to different Java releases, versioning metadata of specific groups of files will be added to the
META-INF directory. This will allow multiple versions of a library for different Java versions to exist in one JAR, a “MRJAR”.
For example, before becoming a MRJAR, the JAR may look like this:
jar root - Foo.class
Adding the attribute:
MANIFEST.MF, we can change the JAR to support pre-Java 9 and post-Java 9 versions of
Foo.class so it looks like this:
jar root - Foo.class - META-INF - versions - 9 - Foo.class
A JDK that doesn’t support MRJARs will only see the classes and resources in the root directory, and ignore the Java 9 version of
Foo.class. Otherwise, the later version of
Foo.class will override the earlier version. The intention is that, running a Java 9 JDK will pick the Java 9 version. Running a Java 10, 11, 12… JDK will pick the 10, 11, 12 version and so on. This will enable libraries to be ready for the next versions of Java in advance of the releases.
Making MRJARs work
There are a number of changes required to support MRJARs. For example the
URLClassLoader has to read the version of the class, determined by the Java platform version. The resource URL produced by the class loader will change to refer to a versioned resource, e.g.
Other changes will be made, such as changing the to read selected versions of the class files.
This is a MRJAR with the module descriptor
module-info.class, that may be present in versioned areas so long as they are identical to the root module descriptor. However they could have different
requires clauses of
jdk.* modules, and different
use clauses. (As a recap,
requires defines the packages that the module depends on at compile and runtime.
uses allows us to express that a particular service is fundamental to the module’s definition). This allows implementations details to change as subsequent versions of the JDK change.
If packages of the JAR are not exported in the module descriptor, when the JAR is on the module path, those classes wont be accessible. However when the JAR is on the classpath, the classes will be accessible in what is described as “an unfortunate consequence of supporting the classpath and modulepath”.
This also means that the public API for MRJARs may be different depending on whether it is placed on the classpath or modulepath.
Should libraries use MRJARs?
The problems with modular MRJARs aside, MRJARs allow libraries and frameworks to prepare for future versions of Java. They can also provide backwards compatibility, without having to publish alternative versions of the library or framework. It removes the need for users of the library or framework to upgrade their versions of Java when the library or framework upgrades. This should incentivise using the latest and greatest language features, without fear that users will be trapped into particular versions of Java.