1. 概述

Java 9 引入了一项实用特性 —— Multi-Release JAR(MRJAR),允许我们在同一个 JAR 包中为不同 Java 版本提供特定实现的类文件。这项功能对于库开发者尤其有价值,可以在不破坏旧版本兼容性的前提下,针对新版本 JVM 使用更优的 API。

本文将介绍如何通过 Maven 配置生成 MRJAR,避免“高版本 API 在低版本运行时报错”的经典踩坑问题。


2. Maven 支持 MRJAR 的背景

Maven 是 Java 生态中最主流的构建工具之一,其默认打包能力虽强,但要支持 MRJAR 需要额外配置。核心在于两点:

✅ 正确编译多个 Java 版本的源码
✅ 生成符合规范的 JAR 结构和 MANIFEST 配置

接下来我们通过一个实际例子演示完整流程。


3. 示例项目结构

我们设想一个场景:
希望 DefaultVersion.version() 方法在 Java 8 中使用 System.getProperty("java.version"),而在 Java 9+ 中改用更现代的 Runtime.version().toString()

3.1 Java 8 实现

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

3.2 Java 9+ 实现

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

✅ 注意:两个类同名、同包,但实现不同,分别放在不同的源目录中。

3.3 主程序入口

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()));
    }

}

3.4 目录结构设计

├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── baeldung
│   │   │           └── multireleaseapp
│   │   │               ├── DefaultVersion.java  # Java 8 版本
│   │   │               └── App.java
│   │   └── java9
│   │       └── com
│   │           └── baeldung
│   │               └── multireleaseapp
│   │                   └── DefaultVersion.java  # Java 9+ 版本

⚠️ 关键点:Java 9+ 的源码放在 src/main/java9 下,这是约定而非强制,但需在 Maven 中显式声明。


4. Maven 配置详解

要让 Maven 正确构建 MRJAR,必须配置两个核心插件:

  • maven-compiler-plugin:负责多版本编译
  • maven-jar-plugin:负责生成正确的 MANIFEST 属性

4.1 Maven Compiler Plugin 配置

我们需要为不同 Java 版本设置独立的编译执行(execution),尤其是 Java 9+ 的部分需要特殊处理。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <executions>
                <!-- 编译 Java 8 主版本 -->
                <execution>
                    <id>compile-java-8</id>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </execution>
                <!-- 编译 Java 9+ 版本 -->
                <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>
                        <multiReleaseOutput>true</multiReleaseOutput>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

关键配置说明:

配置项 作用
<release>9 告诉编译器以 Java 9 模式编译,启用新 API
<compileSourceRoots> 指定额外的源码路径,避免与主源目录冲突
<multiReleaseOutput>true</multiReleaseOutput> ✅ 自动将编译结果输出到 META-INF/versions/9

⚠️ 注意:<release> 必须设置,否则 multiReleaseOutput 不生效,且会导致构建失败。

💡 小贴士:从 maven-compiler-plugin 3.7.1 开始支持 multiReleaseOutput,推荐使用 3.8.0+ 版本。


4.2 Maven JAR Plugin 配置

仅仅编译还不够,JAR 包的 MANIFEST.MF 必须包含 Multi-Release: true 才能被 JVM 识别为 MRJAR。

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

为什么这一步不可少?

  • ❌ 没有该配置 → JVM 只加载根目录下的类,忽略 META-INF/versions/*
  • ✅ 有该配置 → JVM 根据当前运行版本自动选择对应实现

5. 测试验证

构建完成后,用不同 JDK 版本运行 JAR,观察输出差异。

使用 Java 8 运行

java -cp target/multirelease-app-1.0.jar com.baeldung.multireleaseapp.App

输出:

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

使用 Java 14 运行

java -cp target/multirelease-app-1.0.jar com.baeldung.multireleaseapp.App

输出:

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

✅ 成功!Java 14 自动选择了 META-INF/versions/9/ 下的 DefaultVersion.class,并使用了 Runtime.version() 新格式。

📌 提示:虽然我们只提供了 Java 9+ 的实现,但 MRJAR 向后兼容所有更高版本(如 Java 17、21),无需重复打包。


6. 总结

通过合理配置 Maven 插件,我们可以轻松实现 一次打包、多版本兼容 的 MRJAR,适用于以下场景:

  • 库作者希望在新版本中使用新 API(如 VarHandle、Switch Expressions)
  • 维护老系统的同时支持新特性
  • 减少维护多套代码的成本

核心要点回顾

  1. ✅ 使用 maven-compiler-pluginmultiReleaseOutput=true 自动输出版本化类
  2. ✅ 设置 <release> 指定目标版本
  3. ✅ 通过 maven-jar-plugin 添加 Multi-Release: true 到 MANIFEST
  4. ✅ 源码按版本分离,避免冲突

所有示例代码已托管至 GitHub:https://github.com/baeldung/core-java-modules/tree/master/core-java-9-new-features


原始标题:Multi-Release JAR Files with Maven