关于 java-migration
面向 Java 开发者的版本升级指南,提供从评估、修改构建配置到测试的迁移步骤,并说明 Java 与 Spring Boot 的主要兼容变化。
主要功能
- 01Java 版本迁移路径
- 02逐步迁移工作流
- 03版本变化与移除 API 说明
- 04Spring Boot 2.x 到 3.x 指引
- 05迁移检查清单与命令
适用场景
- 01将旧项目升级到较新 Java LTS
- 02排查升级后的编译和运行问题
- 03迁移 Spring Boot 与 Jakarta 包
- 04制定升级前后的检查步骤
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ decebals/ claude-code-java --skill java-migration安装后在 Claude Code 中输入/java-migration确认已加载。
附带文件README.md
详细介绍
Java 迁移技能
逐步指南:如何在不同主版本之间升级 Java 项目。
适用场景
- 用户说“升级到 Java 25”/“从 Java 8 迁移”/“更新 Java 版本”
- 现代化旧项目
- Spring Boot 2.x → 3.x → 4.x 迁移
- 为采用 LTS 版本做准备
迁移路径
Java 8 (LTS) → Java 11 (LTS) → Java 17 (LTS) → Java 21 (LTS) → Java 25 (LTS)
│ │ │ │ │
└──────────────┴───────────────┴──────────────┴───────────────┘
Always migrate LTS → LTS
快速参考:可能的破坏性变更
| 从 → 到 | 主要破坏性变更 |
|---|---|
| 8 → 11 | 移除了 javax.xml.bind、模块系统、内部 API |
| 11 → 17 | 密封类(从预览版变为正式版)、强封装 |
| 17 → 21 | 模式匹配变更、finalize() 已弃用并计划移除 |
| 21 → 25 | 移除了 Security Manager、Unsafe 方法、32 位支持 |
迁移工作流
步骤 1:评估当前状态
# Check current Java version
java -version
# Check compiler target in Maven
grep -r "maven.compiler" pom.xml
# Find usage of removed APIs
grep -r "sun\." --include="*.java" src/
grep -r "javax\.xml\.bind" --include="*.java" src/
步骤 2:更新构建配置
Maven:
<properties>
<java.version>21</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<release>21</release>
</configuration>
</plugin>
Gradle:
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
步骤 3:修复编译错误
运行编译并逐步修复错误:
mvn clean compile 2>&1 | head -50
步骤 4:运行测试
mvn test
步骤 5:检查运行时警告
# Run with illegal-access warnings
java --illegal-access=warn -jar app.jar
Java 8 → 11 迁移
已移除的 API
| 已移除 | 替代方案 |
|---|---|
javax.xml.bind (JAXB) | 添加依赖:jakarta.xml.bind-api + jaxb-runtime |
javax.activation | 添加依赖:jakarta.activation-api |
javax.annotation | 添加依赖:jakarta.annotation-api |
java.corba | 无替代方案(很少使用) |
java.transaction | 添加依赖:jakarta.transaction-api |
sun.misc.Base64* | 使用 java.util.Base64 |
sun.misc.Unsafe(部分) | 尽可能使用 VarHandle |
添加缺失的依赖(Maven)
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>4.0.4</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
模块系统问题
如果使用反射访问 JDK 内部 API,请添加 JVM 标志:
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
Maven Surefire:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
--add-opens java.base/java.lang=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
可采用的新特性
// var (local variable type inference)
var list = new ArrayList<String>(); // instead of ArrayList<String> list = ...
// String methods
" hello ".isBlank(); // true for whitespace-only
" hello ".strip(); // better trim() (Unicode-aware)
"line1\nline2".lines(); // Stream<String>
"ha".repeat(3); // "hahaha"
// Collection factory methods (Java 9+)
List.of("a", "b", "c"); // immutable list
Set.of(1, 2, 3); // immutable set
Map.of("k1", "v1"); // immutable map
// Optional improvements
optional.ifPresentOrElse(
value -> process(value),
() -> handleEmpty()
);
// HTTP Client (replaces HttpURLConnection)
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com"))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
Java 11 → 17 迁移
破坏性变更
| 变更 | 影响 |
|---|---|
| 强封装 | --illegal-access 不再生效,必须显式使用 --add-opens |
| 密封类(正式版) | 如果使用了预览特性 |
| instanceof 模式匹配 | 语法从预览版到正式版有所变化 |
可采用的新特性
// Records (immutable data classes)
public record User(String name, String email) {}
// Auto-generates: constructor, getters, equals, hashCode, toString
// Sealed classes
public sealed class Shape permits Circle, Rectangle {}
public final class Circle extends Shape {}
public final class Rectangle extends Shape {}
// Pattern matching for instanceof
if (obj instanceof String s) {
System.out.println(s.length()); // s already cast
}
// Switch expressions
String result = switch (day) {
case MONDAY, FRIDAY -> "Work";
case SATURDAY, SUNDAY -> "Rest";
default -> "Midweek";
};
// Text blocks
String json = """
{
"name": "John",
"age": 30
}
""";
// Helpful NullPointerException messages
// a.b.c.d() → tells exactly which part was null
Java 17 → 21 迁移
破坏性变更
| 变更 | 影响 |
|---|---|
| switch 模式匹配(正式版) | 与预览版相比有细微的语法差异 |
finalize() 已弃用并计划移除 | 替换为 Cleaner 或 try-with-resources |
| 默认使用 UTF-8 | 如果代码假定使用平台编码,可能会受到影响 |
可采用的新特性
// Virtual Threads (Project Loom) - MAJOR
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> handleRequest());
}
// Or simply:
Thread.startVirtualThread(() -> doWork());
// Pattern matching in switch
String formatted = switch (obj) {
case Integer i -> "int: " + i;
case String s -> "string: " + s;
case null -> "null value";
default -> "unknown";
};
// Record patterns
record Point(int x, int y) {}
if (obj instanceof Point(int x, int y)) {
System.out.println(x + ", " + y);
}
// Sequenced Collections
List<String> list = new ArrayList<>();
list.addFirst("first"); // new method
list.addLast("last"); // new method
list.reversed(); // reversed view
// String templates (preview in 21)
// May need --enable-preview
// Scoped Values (preview) - replace ThreadLocal
ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();
ScopedValue.where(CURRENT_USER, user).run(() -> {
// CURRENT_USER.get() available here
});
Java 21 → 25 迁移
破坏性变更
| 变更 | 影响 |
|---|---|
| 已移除 Security Manager | 依赖它的应用需要采用其他安全方案 |
已移除 sun.misc.Unsafe 方法 | 改用 VarHandle 或 FFM API |
| 已弃用 32 位平台 | 不再支持 x86-32 |
| Record 模式变量为 final | 无法在 switch 中重新赋值给模式变量 |
不允许使用 ScopedValue.orElse(null) | 必须提供非 null 默认值 |
| 限制动态 Agent | 需要 -XX:+EnableDynamicAgentLoading 标志 |
检查 Unsafe 用法
# Find sun.misc.Unsafe usage
grep -rn "sun\.misc\.Unsafe" --include="*.java" src/
# Find Security Manager usage
grep -rn "SecurityManager\|System\.getSecurityManager" --include="*.java" src/
可采用的新特性
// Scoped Values (FINAL in Java 25) - replaces ThreadLocal
private static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();
public void handleRequest(User user) {
ScopedValue.where(CURRENT_USER, user).run(() -> {
processRequest(); // CURRENT_USER.get() available here and in child threads
});
}
// Structured Concurrency (Preview, redesigned API in 25)
try (StructuredTaskScope.ShutdownOnFailure scope = StructuredTaskScope.open()) {
Subtask<User> userTask = scope.fork(() -> fetchUser(id));
Subtask<Orders> ordersTask = scope.fork(() -> fetchOrders(id));
scope.join();
scope.throwIfFailed();
return new Profile(userTask.get(), ordersTask.get());
}
// Stable Values (Preview) - lazy initialization made easy
private static final StableValue<ExpensiveService> SERVICE =
StableValue.of(() -> new ExpensiveService());
public void useService() {
SERVICE.get().doWork(); // Initialized on first access, cached thereafter
}
// Compact Object Headers - automatic, no code changes
// Objects now use 64-bit headers instead of 128-bit (less memory)
// Primitive Patterns in instanceof (Preview)
if (obj instanceof int i) {
System.out.println("int value: " + i);
}
// Module Import Declarations (Preview)
import module java.sql; // Import all public types from module
性能提升(自动)
Java 25 包含多项自动性能提升:
- 紧凑对象头:每个对象占用 8 字节,而非 16 字节
- String.hashCode() 常量折叠:使用 String 键的 Map 查找速度更快
- AOT 类加载:利用提前生成的缓存加快启动速度
- 分代 Shenandoah GC:吞吐量更高,停顿时间更短
使用 OpenRewrite 迁移
# Automated Java 25 migration
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-migrate-java:LATEST \
-Drewrite.activeRecipes=org.openrewrite.java.migrate.UpgradeToJava25
Spring Boot 迁移
Spring Boot 2.x → 3.x
要求:
- Java 17+(必需)
- Jakarta EE 9+(javax.* → jakarta.*)
包名变更:
// Before (Spring Boot 2.x)
import javax.persistence.*;
import javax.validation.*;
import javax.servlet.*;
// After (Spring Boot 3.x)
import jakarta.persistence.*;
import jakarta.validation.*;
import jakarta.servlet.*;
查找并替换:
# Find all javax imports that need migration
grep -r "import javax\." --include="*.java" src/ | grep -v "javax.crypto" | grep -v "javax.net"
自动迁移:
# Use OpenRewrite
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-spring:LATEST \
-Drewrite.activeRecipes=org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0
依赖项更新(Spring Boot 3.x)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
</parent>
Hibernate 5 → 6 变更
// ID generation strategy changed
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // preferred
private Long id;
// Query changes
// Before: createQuery returns raw type
// After: createQuery requires type parameter
// Before
Query query = session.createQuery("from User");
// After
TypedQuery<User> query = session.createQuery("from User", User.class);
常见迁移问题
问题:反射访问被拒绝
症状:
java.lang.reflect.InaccessibleObjectException: Unable to make field accessible
修复:
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
问题:JAXB ClassNotFoundException
症状:
java.lang.ClassNotFoundException: javax.xml.bind.JAXBContext
修复: 添加 JAXB 依赖(参见 Java 8→11 部分)
问题:Lombok 无法正常工作
修复: 将 Lombok 更新到最新版本:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
问题:Mockito 测试失败
修复: 更新 Mockito:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
迁移清单
迁移前
- 记录当前 Java 版本
- 列出所有依赖及其版本
- 确定内部 API(
sun.*、com.sun.*)的使用情况 - 检查框架兼容性(Spring、Hibernate 等)
- 备份/创建分支
迁移期间
- 更新构建工具配置
- 添加缺少的 Jakarta 依赖
- 修复
javax.*→jakarta.*导入(如果使用 Spring Boot 3) - 按需添加
--add-opens标志 - 更新 Lombok、Mockito 和其他工具
- 修复编译错误
- 运行测试
迁移后
- 移除不必要的
--add-opens标志 - 采用新的语言特性(records、var 等)
- 更新 CI/CD 流水线
- 记录所做的更改
快捷命令
# Check Java version
java -version
# Find internal API usage
grep -rn "sun\.\|com\.sun\." --include="*.java" src/
# Find javax imports (for Jakarta migration)
grep -rn "import javax\." --include="*.java" src/
# Compile and show first errors
mvn clean compile 2>&1 | head -100
# Run with verbose module warnings
java --illegal-access=debug -jar app.jar
# OpenRewrite Spring Boot 3 migration
mvn org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-spring:LATEST \
-Drewrite.activeRecipes=org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0
版本兼容性矩阵
| 框架 | Java 8 | Java 11 | Java 17 | Java 21 | Java 25 |
|---|---|---|---|---|---|
| Spring Boot 2.7.x | ✅ | ✅ | ✅ | ⚠️ | ❌ |
| Spring Boot 3.2.x | ❌ | ❌ | ✅ | ✅ | ✅ |
| Spring Boot 3.4+ | ❌ | ❌ | ✅ | ✅ | ✅ |
| Hibernate 5.6 | ✅ | ✅ | ✅ | ⚠️ | ❌ |
| Hibernate 6.4+ | ❌ | ❌ | ✅ | ✅ | ✅ |
| JUnit 5.10+ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Mockito 5+ | ❌ | ✅ | ✅ | ✅ | ✅ |
| Lombok 1.18.34+ | ✅ | ✅ | ✅ | ✅ | ✅ |
LTS 支持时间线:
- Java 21:Oracle 免费支持至 2028 年 9 月
- Java 25:Oracle 免费支持至 2033 年 9 月
评论
支持 Markdown