mcpField
J
java-migration

java-migration

github.com/decebals开发761333 次安装7 周前更新
代码仓库

关于 java-migration

面向 Java 开发者的版本升级指南,提供从评估、修改构建配置到测试的迁移步骤,并说明 Java 与 Spring Boot 的主要兼容变化。

主要功能

  1. 01Java 版本迁移路径
  2. 02逐步迁移工作流
  3. 03版本变化与移除 API 说明
  4. 04Spring Boot 2.x 到 3.x 指引
  5. 05迁移检查清单与命令

适用场景

  1. 01将旧项目升级到较新 Java LTS
  2. 02排查升级后的编译和运行问题
  3. 03迁移 Spring Boot 与 Jakarta 包
  4. 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 8Java 11Java 17Java 21Java 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