本文针对“Maven 用哪个版本 JDK,编译出的应用就是哪个版本”这一常见误解进行深入剖析,系统阐述 Maven 编译过程中两个核心 JDK 概念的差异:
核心内容包括:
配置对比:对比默认行为(未配置时 Maven 使用运行 JDK 版本作为目标版本)与明确配置目标版本(通过 maven.compiler.release 固定输出字节码版本)的差异
关键区分:详细对比 maven.compiler.release 与 java.version 两个配置项的作用对象、配置方式、影响范围及典型值示例,澄清前者控制编译目标、后者控制运行环境的本质区别
实战验证:提供完整的测试项目代码(Test.java + pom.xml)、Docker 环境测试方案及验证脚本,读者可自行运行验证
字节码对照:附 JDK 8/11/17/21 对应的 Major Version 与编译参数速查表
重要限制:强调不能反向编译(低版本 JDK 无法编译高版本字节码)及 API 兼容性约束(即使字节码版本正确,也不能使用高版本 API)
通过本文,读者将理解 Maven 跨版本编译的原理,掌握让高版本 JDK 编译出低版本兼容字节码的配置方法,实现开发环境使用最新 JDK 特性、生产环境保持低版本 JDK 兼容性的灵活构建策略。
不是的! Maven 使用的 JDK 版本和编译出的应用目标版本可以不同。
Maven 编译过程涉及两个 JDK 概念:
它们可以完全独立配置。
Bash# Maven 运行在 JDK 21 上
$ mvn -version
Apache Maven 3.9.14
Java version: 21.0.1, vendor: Oracle Corporation
# 但通过配置,可以编译出 JDK 8 兼容的字节码
$ mvn clean package -Dmaven.compiler.release=8
# 检查字节码版本(major version 52 = JDK 8)
$ javap -verbose target/classes/MyClass.class | grep "major"
major version: 52 # 这是 JDK 8
Bash# Maven 运行在 JDK 8 上
$ mvn -version
Java version: 1.8.0_392
# 默认编译出 JDK 8 字节码
$ mvn clean package
# 检查字节码版本
$ javap -verbose target/classes/MyClass.class | grep "major"
major version: 52 # JDK 8
XML<!-- 未配置编译器版本时 -->
<project>
<!-- 没有任何 compiler 配置 -->
</project>
结果:Maven 使用运行 JDK 的版本作为目标版本
XML<project>
<properties>
<!-- 明确指定目标版本 -->
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!-- 或使用 release(推荐) -->
<maven.compiler.release>8</maven.compiler.release>
</properties>
</project>
结果:无论运行 JDK 是什么版本,都编译出 JDK 8 字节码
Tip. 如果配置是这样的
XML<project>
<properties>
<!-- 这里指定 Maven 进程本身(以及它调用的编译器) -->
<java.version>8</java.version>
</properties>
</project>
| 对比维度 | maven.compiler.release | java.version |
|---|---|---|
| 作用对象 | 编译出的 .class 字节码 | Maven 进程本身(以及它调用的编译器) |
| 定义位置 | pom.xml 的 <properties> 中 | 系统环境变量或 JAVA_HOME |
| 配置方式 | Maven 项目配置 | 命令行 export JAVA_HOME=... 或 IDE 设置 |
| 影响范围 | 仅影响当前项目的编译输出 | 影响整个 Maven 执行过程及所有插件 |
| 典型值示例 | 8, 11, 17, 21 | 1.8, 11, 17, 21(注意格式差异) |
| 是否可跨版本 | ✅ 可以用高版本 JDK 编译低版本目标 | ❌ 只能是实际安装的 JDK 版本 |
| 控制字节码版本 | ✅ 是 | ❌ 否(仅决定运行环境) |
| 限制 API 使用 | ✅ 是(--release 会限制只能使用目标版本的 API) | ❌ 否 |
| 限制语言特性 | ✅ 是(如 switch 表达式、文本块等) | ❌ 否 |
| 默认值 | 无(不配置时等于运行 JDK 的版本) | 当前运行 Maven 的 JDK 实际版本 |
| 能否被命令行覆盖 | ✅ 可以:-Dmaven.compiler.release=8 | ❌ 不能(需改 JAVA_HOME 环境变量) |
| 与 Maven 版本的关系 | 需要 Maven 3.6.0+ 或 Compiler Plugin 3.6.0+ | 与 Maven 版本无关 |
| 配置错误的后果 | 编译失败或字节码版本不匹配 | Maven 无法运行或插件不兼容 |
创建一个测试项目来验证:
Java// src/main/java/Test.java
public class Test {
public static void main(String[] args) {
System.out.println("Java version: " + System.getProperty("java.version"));
System.out.println("Class file version: " + getClassFileVersion());
}
private static String getClassFileVersion() {
// 通过字节码判断版本
String version = System.getProperty("java.class.version");
return version;
}
}
XML<project>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.odboy</groupId>
<artifactId>cutejava</artifactId>
<version>1.0.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<!-- 目标 JDK 8 -->
<maven.compiler.release>8</maven.compiler.release>
</properties>
<build>
<!-- 和应用名一致 -->
<finalName>cutejava</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- 跳过单元测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</project>
Docker# Dockerfile - 使用 JDK 21 运行 Maven FROM eclipse-temurin:21-jdk RUN apt-get update && apt-get install -y maven WORKDIR /app COPY pom.xml . COPY src ./src # Maven 运行在 JDK 21 上 RUN mvn -version RUN mvn clean package # 检查字节码版本 RUN javap -verbose target/classes/Test.class | grep "major"
创建一个验证脚本来演示不同组合:
Bash#!/bin/bash
# verify-jdk-compatibility.sh
echo "=== 测试 1: JDK 21 编译 JDK 8 目标 ==="
docker run --rm -v $(pwd):/app -w /app \
eclipse-temurin:21-jdk \
sh -c "mvn clean compile -Dmaven.compiler.release=8 && \
javap -verbose target/classes/Test.class | grep 'major'"
echo ""
echo "=== 测试 2: JDK 8 编译 JDK 8 目标 ==="
docker run --rm -v $(pwd):/app -w /app \
eclipse-temurin:8-jdk \
sh -c "mvn clean compile -Dmaven.compiler.release=8 && \
javap -verbose target/classes/Test.class | grep 'major'"
echo ""
echo "=== 测试 3: JDK 21 默认编译(无配置)==="
docker run --rm -v $(pwd):/app -w /app \
eclipse-temurin:21-jdk \
sh -c "mvn clean compile && \
javap -verbose target/classes/Test.class | grep 'major'"
| 目标 JDK | Major Version | 编译参数 |
|---|---|---|
| JDK 8 | 52 | -source 1.8 -target 1.8 或 --release 8 |
| JDK 11 | 55 | --release 11 |
| JDK 17 | 61 | --release 17 |
| JDK 21 | 65 | --release 21 |
YAMLbuild:
image: maven:3.9.14-eclipse-temurin-21 # 使用 JDK 21 构建
script:
# 但目标是 JDK 8 环境
- mvn clean package -Dmaven.compiler.release=8
artifacts:
paths:
- target/*.jar
deploy:
image: openjdk:8-jre # 生产环境用 JDK 8 运行
script:
- java -jar target/*.jar
Bash# 编译多个目标版本
mvn clean package -Dmaven.compiler.release=8
mvn clean package -Dmaven.compiler.release=11
mvn clean package -Dmaven.compiler.release=17
Bash# ❌ 错误:不能用 JDK 8 编译出 JDK 21 的字节码
mvn clean package -Dmaven.compiler.release=21 # 如果运行 JDK 是 8,会失败
# ✓ 正确:只能用高版本 JDK 编译低版本目标
mvn clean package -Dmaven.compiler.release=8 # JDK 21 可以编译 JDK 8
即使字节码版本正确,也不能使用高版本 API:
Java// ❌ 即使配置了 --release 8,这行代码也会编译失败
List<String> list = List.of("a", "b"); // JDK 9+ API
// ✓ 必须使用 JDK 8 的 API
List<String> list = Arrays.asList("a", "b");
maven.compiler.source/target 或 maven.compiler.release,可以让高版本 JDK 编译出低版本兼容的字节码这种灵活性让开发者可以在开发环境使用最新的 JDK 特性(如更好的性能、工具支持),同时保持对生产环境低版本 JDK 的兼容性。


本文作者:Odboy
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 CC 4.0 BY-SA 许可协议。转载请注明出处!