skip to content

What is a Multi-Release JAR, how do you build one with Maven, and what does the manifest contain?

level: seniorimportance: should knowfreq 30%

answer

  1. META-INF/versions/<N>/
  2. Multi-Release: true header
  3. highest version <= running JDK
  4. identical public API across versions
  5. JDK 8 ignores header => sees base only

basics

~10 s

A Multi-Release JAR ships one base set of classes plus version-specific overrides under META-INF/versions/<N>/. Its manifest has Multi-Release: true, so a JDK 17 runtime prefers versions/17 classes over the base.

solid answer

~40 s

A Multi-Release JAR (MRJAR, since Java 9) lets one JAR target multiple JVM versions: base classes live at the root (compiled to the lowest supported bytecode), and overrides for newer runtimes live under META-INF/versions/<release>/ with matching package paths. The manifest must contain Multi-Release: true — that's what makes the class loader look in versions/<N> first, choosing the highest version <= the running JDK. In Maven you build it by compiling each source set to its own output via separate maven-compiler-plugin executions (multiReleaseOutput) or a multi-module/toolchains setup, then telling maven-jar-plugin to add the header with <archive><manifestEntries><Multi-Release>true</Multi-Release></manifestEntries> and placing the version-specific classes under the right directories. Public APIs must be identical across versions; only implementations differ. Common use: a library that uses a Java 9+ API on new JDKs but a fallback on Java 8.

code

xml · 5 lines
xml
<archive>
  <manifestEntries>
    <Multi-Release>true</Multi-Release>
  </manifestEntries>
</archive>

go deeper

for a junior

Know it exists: one JAR, version-specific classes under META-INF/versions, header Multi-Release: true.

for a middle

Know the directory layout and the highest-<=-runtime selection rule.

for a senior

Know how to build it in Maven (compiler executions + jar manifestEntries) and the identical-API constraint.

for a principal

Weigh MRJAR build/tooling complexity vs alternatives (separate artifacts, jlink, min-target compile) for a library's JDK-support matrix.

## What an MRJAR is Introduced in **Java 9 (JEP 238)**, a **Multi-Release JAR** packages multiple implementations of the same classes so a single artifact runs optimally on different JDKs. Layout: ``` app.jar |- com/example/Foo.class (base, e.g. Java 8 bytecode) |- META-INF/MANIFEST.MF (Multi-Release: true) |- META-INF/versions/11/com/example/Foo.class |- META-INF/versions/17/com/example/Foo.class ``` At runtime a JDK 17 loader picks `versions/17/...Foo`, a JDK 11 picks `versions/11/...Foo`, and a JDK 8 (which ignores the header entirely) sees only the base `Foo`. ## The required manifest header ``` Manifest-Version: 1.0 Multi-Release: true ``` Without `Multi-Release: true`, the `versions/` directory is treated as ordinary, ignored data — the override never activates. The runtime selects the **highest** versioned directory whose number is `<= ` the running feature release. ## Rules - **Public API must be identical** across base and all versioned copies (same class/method signatures). Only the implementation may differ; tooling and `jar --validate` check this. - Versioned classes can use newer-JDK APIs the base cannot. - `module-info.class` may also be versioned. ## Building with Maven There's no single magic flag; the common recipe uses multiple compiler executions targeting different releases plus the jar-plugin header: ```xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifestEntries> <Multi-Release>true</Multi-Release> </manifestEntries> </archive> </configuration> </plugin> ``` For the bytecode, you either: - Use **maven-compiler-plugin** with `<release>` per execution and `<multiReleaseOutput>true</multiReleaseOutput>` so the 9+ output lands under `target/classes/META-INF/versions/N`, **or** - Use a **multi-module** layout (a `base` module + a `java17` module) and assemble, often with the **moditect** plugin or a dedicated MRJAR build helper. Validate the result with `jar --validate --file target/app.jar`. ## When to use / avoid Use for libraries that must support old and new JDKs while exploiting new APIs (e.g., stack-walking, `var handles`). Avoid when you control the runtime — just compile to one target. MRJARs complicate builds, IDE indexing, and some older tooling that doesn't understand `versions/`.

  • Which versioned directory does a JDK 14 runtime use if the JAR has versions/11 and versions/17?
    versions/11 — the loader picks the highest version number that is <= the running feature release (14), so 17 is skipped and 11 wins.
  • What happens on Java 8 with a Multi-Release JAR?
    Java 8 predates MRJARs, ignores the Multi-Release header and META-INF/versions, and loads only the base/root classes.
  • Can the versioned class add a new public method the base doesn't have?
    No — the public API must be identical across versions; only the implementation may differ, and jar --validate enforces this.

Like a power adapter with swappable plug heads: the device (your API) is the same everywhere, but the JAR picks the right plug head (implementation) for the country (JDK) it's plugged into.

saying these in an interview costs you the question

  • Forgetting the Multi-Release: true header (versions/ is then ignored)
  • Letting the public API diverge between base and versioned classes
  • Assuming the runtime picks the exact-matching version rather than the highest <= the running JDK
  • Believing Java 8 will honor versions/9 overrides

context