Week 140 — What are multi-release JARs and how are they structured?
Question of the Week #140
What are multi-release JARs and how are they structured?
4 Replies
Multi-release JAR files are JAR files that contain different code depending on the Java version they are executed with. This is possible for both library and application JARs.
They have the following characteristics:
- The
META-INF/MANIFEST.MF
file has an Multi-Release: true
attribute.
- The JAR contains a META-INF/versions
directory containing another directory with subdirectories corresponding to Java version numbers (e.g. 9
, 17
or 21
). These subdirectories can then contain class files in the same format as they would be in the JAR root.
When a class in a multi-release JAR is loaded, the JVM first looks through all version-specific subdirectories (META-INF/versions/<number>
) corresponding to a JDK version lower or equal to the one used to execute the JAR starting with the current version. If none of these directories contain the class to load, it looks at the JAR root as it if it wouldn't be loaded from a multi-release JAR.
For example, consider a JAR file with the following format:
With a JAR like this (and the MANIFEST.MF
containing Multi-Release: true
), different classes and different versions of the same classes are available depending on the JDK version:
- When the class MultiVersion
is loaded, the JVM would resolve META-INF/versions/17/MultiVersion.class
with JDK 17 or later, META-INF/versions/9/MultiVersion.class
between JDK 9 (included) and JDK 17 (excluded) and the MultiVersion.class
file in the JAR root on JDK 8 or earlier.
- The class AvailableWith9
is available on JDK 9 or later.
- AvailableWith17
can be loaded with JDK 17 or later.
- OverriddenOnce
resolves to META-INF/versions/9/OverriddenOnce.class
with JDK 9 or later and earlier versions would use the OverriddenOnce.class
file in the JAR root.
- Accessing the class OnlyInRoot
works with any JDK version and uses the OnlyInRoot.class
file in the JAR root.Multi-Release JAR files were added in JDK 9 and older Java versions ignore the
META-INF/versions
directory for class file resolution. This feature is commonly used to add a module descriptor (module-info.java
/module-info.class
) that is available with JDK 9 or later but not used with older JDKs that do not support Java modules.📖 Sample answer from dan1st
Multi release JARs are a niche feature that was introduced in JDK 9. The idea is that library authors can build most of their code against a "lowest common denominator" JDK, such as JDK 8, but introduce alternative implementations of classes that make use of more recent Java releases.
The version-specific classes are placed in the JAR file under
META-INF/versions/<minimum JDK release>/
, e.g. META-INF/versions/11/
for classes that require Java 11 or greater. The MANIFEST.MF
file also needs a Multi-Release: true
attribute.
Note that the mechanism for structuring the source is dependent on the tooling that the project uses. For instance, Maven's compiler plugin supports at least 4 different ways of structuring code, along with a suitable POM definition. Regardless of how the source is structured, however, the output must match the structure above, with the "main" class files starting in the root of the JAR, and versions-specific classes under META-INF/versions/
.
Also of note is that the release-specific classes must have an identical public/protected interface to the general classes.
As a concrete example, imagine that you have a library that supports Java 8+. It defines an interface with 3 implementations:
You also have a class that generates some random data:
Ideally, clients would only extend CustomStuffWriter
, rather than fully implement the interface themselves or extend Normal...
or Enhanced...
. You would also like to take advantage of recent enhancements to random number generation on the Java platform. However, your customer support agreements require you to continue supporting Java 8 for another 5 years. Multi release JARs can help you to evolve the code base for more recent Java versions, without abandoning support for Java 8.
To do that, you would create some separate source directory/project (again, depending on the toolchain), and re-define the classes that can take advantage of newer features:
After compiling and packaging, the multi release JAR will contain:
When this JAR is run on a JRE 8 or higher, but less than 17, the "default" classes will be loaded and run. But when it is run on a JRE 17+, then the release-specific classes will be loaded and run, alongside any of the "default" classes that do not have a release-specific variant.
Submission from dangerously_casual