Skip to content

Java 17+ 与 Spring Boot 3.x:原理、版本边界和迁移全过程

Spring Boot 2.7 + JDK 8 仍然是大量企业存量系统的重要组合,但新系统不能永远停留在这条基线上。Spring Boot 3 建立在 Spring Framework 6 和 Java 17 之上,并迁移到 Jakarta EE 命名空间,同时增强了 AOT、原生镜像、可观测性和云原生运行能力。

这一页采用“双轨学习”方式:既讲清旧项目为什么还能运行,也讲清新项目为什么应该评估 Java 17+ 与 Boot 3.x。重点不是背一张差异表,而是理解每项变化发生在哪一层、为什么必须改、不改会出现什么错误、怎样用证据完成迁移。

一、学完后必须具备什么能力

  1. 区分 JDK、Java 语言级别、JVM、Spring Framework、Spring Boot 和 Jakarta EE 的职责。
  2. 说明为什么 Spring Boot 3 的最低 Java 基线是 17,而不是 8 或 11。
  3. 说明 javax.* 为什么迁移到 jakarta.*,以及为什么不能只做字符串替换。
  4. 正确区分 Boot 2.6、2.7、3.0、3.2 等代际,不把某个小版本能力说成整个 Boot 3 都有。
  5. 分别创建可运行的 Boot 2.7/JDK 8 和 Boot 3.x/Java 17 Web 服务。
  6. 看懂自动配置候选机制、构造器绑定、Servlet 容器、参数校验、Security、Observability 的变化。
  7. 设计一条可回滚、可验证、可灰度的商业系统迁移流程。
  8. 根据异常类型判断问题来自字节码、包名、依赖二进制兼容、配置迁移还是运行环境。

本章是版本迁移总入口。需要继续深入时按下面的学习顺序进入专题页:

  1. Java 17 现代基线:语言、JVM、强封装与升级
  2. Spring Boot 3.x 内部执行链:启动、自动配置、Jakarta、AOT 与观测
  3. Java 17 与 Spring Boot 3.x 独立面试题

二、双轨版本观:旧基线与新基线不是二选一

轨道常见组合主要用途关键约束
历史维护JDK 7 + 传统 Spring 或很早期 Boot只用于接手历史系统生态停止演进、安全和运维成本高,不适合新建
企业存量JDK 8 + Spring Boot 2.7.x维护仍有生命周期治理的存量服务使用 Spring 5.3、javax.*;升级依赖时必须检查兼容性
现代基线Java 17+ + Spring Boot 3.x新项目与现代化升级Spring 6、Jakarta 命名空间、依赖必须整体兼容
进一步演进Java 21 + 支持它的 Boot 3.x 小版本需要虚拟线程等新能力Java 21 能力不能错误地写成 Java 17 能力

选择版本时必须查目标 Spring Boot 精确版本的系统要求和依赖清单。3.x 是一个持续演进的系列,不代表所有 3.x 小版本支持完全相同的 JDK 上限、Gradle、Maven、Tomcat 或第三方组件。

mermaid
flowchart TD
    A["确认是存量维护还是新建系统"] --> B["记录 JDK、Boot、Cloud 精确版本"]
    B --> C["检查支持周期与依赖兼容矩阵"]
    C --> D["存量系统制定分阶段迁移<br/>新系统选择受支持的现代基线"]
    D --> E["锁定 BOM 与构建 JDK"]
    E --> F["建立可重复构建和回滚基线"]

存量系统与新系统在第四步采取不同决策:存量系统先评估并分阶段升级,新系统优先选择仍受支持的 Java LTS 与匹配的 Boot 版本;流程图纵向合并这两个结果是为了保证窄屏可读。

为什么不能“所有服务直接升最新版”

微服务系统的依赖不只有 Boot。还包括 Spring Cloud 发布列车、Spring Cloud Alibaba、注册中心客户端、数据库驱动、ORM、MQ、APM Agent、日志实现、Servlet Filter、公司公共 Starter。任何一个 Jar 若仍依赖旧 javax 类型,都可能在启动或请求执行时失败。

为什么也不能“旧系统能跑就永远不升”

长期停留在旧基线会累积 JDK 安全补丁、框架维护、容器镜像、TLS 算法、依赖漏洞和人才维护成本。正确方式是把升级当作工程项目:先盘点,再补测试,再迁依赖,再灰度,而不是无限拖延或一次性豪赌。

三、先分清变化发生在哪一层

负责什么Boot 3 相关变化示例
Java 语言语法和标准库 API文本块、模式匹配、Record、密封类;具体能力受 Java 版本限制
JVM / class 文件加载、验证、JIT、GCBoot 3 运行时最低要求 Java 17;低版本 JVM 无法加载高版本字节码
Spring Framework 6IoC、AOP、事务、MVC、WebFluxJava 17 基线、Jakarta EE 9+、AOT 基础、HTTP 接口等
Spring Boot 3依赖管理、自动配置、启动、ActuatorBoot 3 自动配置注册、Observability、原生镜像支持等
Jakarta EE / ServletWeb、验证、持久化等规范 APIjavax.servlet 迁到 jakarta.servlet,容器也必须实现新规范
Spring Cloud微服务组件集成必须选择与 Boot 3 精确兼容的 Release Train
第三方生态ORM、驱动、Agent、Starter每个组件都必须提供 Spring 6/Jakarta 兼容版本

一个 ClassNotFoundException 可能不是 Boot 的启动流程错了,而是第三方 Jar 仍在找 javax.servlet.Filter;一个 UnsupportedClassVersionError 也不是 Spring Bean 错了,而是运行 JVM 低于编译字节码版本。排查前必须先定位层次。

四、为什么 Spring Boot 3 以 Java 17 为最低基线

Spring Boot 3 基于 Spring Framework 6,而 Spring Framework 6 将 Java 17 设为最低基线。因此 Boot 3 应用无法在 JDK 8 或 11 JVM 上运行。这是运行时硬约束,不是“建议使用”。

4.1 基线提升给框架带来什么

框架不再需要保留大量旧 JDK 兼容分支,可以使用更现代的 JVM、语言和标准库能力,并把维护和测试资源集中在受支持的平台上。应用也能获得 Java 9 之后持续积累的 JVM、GC、容器识别、诊断和语言能力。

4.2 编译 JDK、--release 和运行 JDK不是一回事

概念含义常见错误
编译 JDK执行 javac 的 JDK本机 IDE 用 17,CI 却仍用 8
--release限制目标 class 版本和可见标准库 API只改 source/target,仍误用高版本标准库
运行 JDK真正执行 Jar 的 JVM编译成功,部署后 UnsupportedClassVersionError
Maven Toolchains为构建插件显式选择 JDKMaven 自身和编译器使用了不同 JDK

Java 17 编译出的默认 class major version 是 61,Java 8 是 52。Java 8 JVM 不认识版本 61 的 class 文件。

mermaid
flowchart TD
    A["Javac生成class文件"] --> B["class头部携带major version"]
    B --> C["目标JVM读取并校验"]
    C --> D["比较class版本与JVM支持上限"]
    D --> E["支持则继续验证和链接<br/>不支持则抛出版本错误"]

4.3 Java 17 可以用哪些现代能力

  • Record 适合表达不可变数据载体,但不要机械替换 JPA Entity。
  • 文本块适合长 SQL、JSON 和测试数据。
  • instanceof 模式匹配减少重复强制转换。
  • 密封类可限制领域状态或结果类型的实现集合。
  • 更现代的 GC 和 JVM 诊断能力有利于生产运行。

这些能力是可选的业务代码改进,不是 Boot 3 能启动的原因。Boot 3 能否运行取决于框架和 JVM 基线,不能说“因为用了 Record 所以必须 Boot 3”。

4.4 Java 21 能力必须单独标记

虚拟线程是 Java 21 正式能力,不是 Java 17 能力。某些 Boot 3.x 小版本提供虚拟线程相关配置支持,但必须同时满足:

  1. 运行时确实是 Java 21 或更高兼容版本。
  2. Boot 小版本支持相应集成。
  3. 依赖库没有大量 synchronized 固定、ThreadLocal 滥用或不适合的线程亲和假设。
  4. 数据库连接池等稀缺资源仍要限流;虚拟线程不会凭空增加数据库连接。

五、javax.*jakarta.*:为什么是二进制不兼容迁移

Java EE 移交 Eclipse 基金会后演进为 Jakarta EE。Jakarta EE 9 将大量规范 API 的包名从 javax.* 改成 jakarta.*。Spring Framework 6 面向 Jakarta 命名空间,因此 Boot 3 Web 应用也进入新的类型体系。

5.1 常见包名变化

Boot 2 / Spring 5Boot 3 / Spring 6
javax.servlet.*jakarta.servlet.*
javax.validation.*jakarta.validation.*
javax.persistence.*jakarta.persistence.*
javax.annotation.*jakarta.annotation.*
javax.transaction.*jakarta.transaction.*

注意:不是所有 javax.* 都迁移。例如 Java SE 自带的 javax.cryptojavax.netjavax.sql 不应批量替换。它们属于 Java SE,不属于此次 Jakarta EE 包迁移范围。

5.2 为什么改一个 import 还不够

JVM 用完全限定类名识别类型。javax.servlet.Filterjakarta.servlet.Filter 是两个不同的类型,即使方法看起来相同也不能互相赋值。旧 Filter Jar 编译后的常量池仍然引用 javax/servlet/Filter,改业务源码不会重写第三方 Jar。

mermaid
flowchart TD
    A["Boot 3启动并加载组件"] --> B["JVM解析第三方Jar常量池"]
    B --> C["检查规范类型是 jakarta 还是旧 javax"]
    C --> D["Jakarta兼容则继续创建组件"]
    D --> E["旧类型失败时定位来源Jar"]
    E --> F["升级、替换或重新编译组件"]

第三步的真实结果是二选一:兼容组件继续启动;仍引用旧类型的组件会在类加载、方法链接或类型转换阶段失败。图中将失败处理画在主链下方,便于展示完整迁移处置路径。

5.3 常见失败表现

  • ClassNotFoundException: javax.servlet.Filter:旧库仍需要旧 Servlet API。
  • NoClassDefFoundError: javax/validation/...:验证库或自定义 Starter 未迁移。
  • NoSuchMethodError:编译期和运行期依赖版本不一致。
  • Bean 类型不匹配:接口一边是 javax,实现一边是 jakarta
  • 应用启动成功但请求失败:某个 Filter、序列化器或懒加载路径到请求时才触发。

5.4 Servlet 容器为什么也要一起升级

Boot 2.7 常见 Tomcat 9,面向旧 javax.servlet 命名空间;Boot 3 常见 Tomcat 10.1,面向 Jakarta Servlet 6.0。不能把旧 Tomcat 9 当作 Boot 3 的外部容器,也不能认为“都是 Tomcat 所以兼容”。

六、Spring Boot 3 核心变化及其原理

6.1 自动配置候选类注册机制

常见说法“Boot 2 用 spring.factories,Boot 3 用 AutoConfiguration.imports”方向大致正确,但版本边界需要更精确:

版本代际自动配置候选注册
Boot 2.6 及更早常见版本META-INF/spring.factoriesEnableAutoConfiguration key
Boot 2.7已支持并推荐 AutoConfiguration.imports,旧方式仍可兼容
Boot 3使用 AutoConfiguration.imports 注册自动配置;旧 spring.factories 自动配置候选机制已移除

Boot 3 文件位置:

text
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

内容每行一个自动配置类:

text
com.example.collect.autoconfigure.CollectClientAutoConfiguration

这不表示 spring.factories 文件在 Boot 3 世界里完全不存在;Spring/Boot 的其他工厂扩展仍可能使用它。被移除的是通过旧 key 注册自动配置候选类的方式,回答时要限定语境。

自动配置的根本原理没有变化:候选类被导入,条件注解根据 classpath、配置、Web 类型和已有 BeanDefinition 做判断,匹配后注册 BeanDefinition,最后仍由 Spring IoC 在 refresh() 中创建 Bean。

6.2 @ConfigurationProperties 与构造器绑定

Boot 2.2 到 2.7 的不可变属性类常在类型上标注 @ConstructorBinding。Boot 3 中,只有一个参数化构造器时会推断构造器绑定,通常不再需要在类型上标注;存在多个构造器时才需要明确选择绑定构造器,并注意注解位置和包名变化。

不要看到 Boot 3 就把所有属性类改成 Record。Record 很适合不可变配置,但嵌套结构、默认值、校验、元数据生成和团队可读性仍需评估。

6.3 Spring MVC 与 Servlet 栈

请求主链并没有因为 Boot 3 而消失:

mermaid
flowchart TD
    A["客户端请求"] --> B["Tomcat 10.1连接器"]
    B --> C["jakarta.servlet.Filter链"]
    C --> D["DispatcherServlet"]
    D --> E["HandlerMapping定位Controller"]
    E --> F["参数解析、转换和jakarta.validation校验"]
    F --> G["Controller与Service"]
    G --> H["消息转换器序列化响应"]

变化的是规范类型和组件版本,不是 MVC 的核心职责。迁移时如果只验证“应用端口起来了”,没有覆盖 Filter、参数校验、文件上传、异常处理和序列化,就无法证明迁移成功。

6.4 Spring Security 6 集成变化

Boot 3 对应 Spring Security 6。旧式继承 WebSecurityConfigurerAdapter 的配置方式已退出主线,推荐声明 SecurityFilterChain Bean;授权匹配 API 也应按 Security 6 的方式迁移。

java
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/actuator/health").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated())
            .httpBasic(Customizer.withDefaults());
        return http.build();
    }
}

迁移不能只求编译通过,还要验证匹配顺序、默认安全行为、CSRF、CORS、Session/JWT、异常响应和方法级授权。安全规则变化属于业务风险,不只是 API 替换。

6.5 Observability:指标、Trace 和日志关联

Boot 3 的可观测性主线建立在 Micrometer Observation 上。Spring Cloud Sleuth 不再是新代际主线,分布式追踪通常迁移到 Micrometer Tracing,并通过 OpenTelemetry 或 Brave 等桥接实现传播和上报。

一次 HTTP、数据库或消息调用可抽象为 Observation:

mermaid
flowchart TD
    A["业务操作开始"] --> B["创建Observation上下文"]
    B --> C["计时并写入低基数标签"]
    C --> D["Tracing Handler创建或关联Span"]
    D --> E["跨进程传播trace上下文"]
    E --> F["结束Observation并上报指标与Trace"]

必须控制标签基数。把 userId、订单号、完整 URL 放进指标标签,会制造海量时间序列;这些高基数值更适合日志或 Trace 属性。

6.6 AOT 与 Native Image 为什么需要“闭世界”信息

传统 JVM 可以在运行时扫描 classpath、反射加载类和生成代理。原生镜像在构建期尽量确定可达代码,运行时动态性受到更强约束,因此框架需要提前生成初始化代码,并为反射、资源、序列化和代理提供 Runtime Hints。

mermaid
flowchart TD
    A["应用源码和依赖"] --> B["Spring AOT分析Bean与配置"]
    B --> C["生成初始化代码和Runtime Hints"]
    C --> D["Native Image闭世界分析"]
    D --> E["生成原生可执行文件"]
    E --> F["更快启动和较低空闲内存"]

原生镜像不是“全面比 JVM 快”。它常在启动速度和空闲内存上有优势,但构建更慢、动态特性需要声明、峰值吞吐和调试方式要实测。长时间运行且 JIT 充分预热的服务,不一定适合直接改成 Native。

6.7 HTTP 客户端能力要按小版本区分

  • WebClient 来自 Spring WebFlux,可用于响应式或非阻塞链路,并非 Boot 3 新增。
  • HTTP Interface 在 Spring Framework 6 提供声明式接口能力。
  • RestClient 属于 Spring Framework 6.1 / Boot 3.2 代际,不应声称所有 Boot 3.0 项目都有。
  • RestTemplate 并非一升级 Boot 3 就立即不能运行,但新同步客户端设计应评估 RestClient 的版本前提。

七、依赖管理:为什么代码没改也可能启动失败

Spring Boot BOM 管理一组经过协同测试的依赖版本。业务项目手工覆盖 Jackson、Netty、Hibernate、Logback 或 Tomcat 版本,可能破坏这组约束,引发 NoSuchMethodError、序列化差异或启动异常。

7.1 正确依赖顺序

  1. 选择 Java 运行基线。
  2. 选择受支持的 Boot 精确版本。
  3. 按官方兼容矩阵选择 Spring Cloud Release Train。
  4. 再选择匹配的 Spring Cloud Alibaba、数据库、MQ 和公司 Starter。
  5. 用 BOM 管理版本,少量覆盖必须记录原因和验证证据。

7.2 依赖树应该查什么

bash
mvn dependency:tree
mvn dependency:tree -Dverbose
mvn help:effective-pom

重点寻找:同一组件多版本、旧 javax API、被手工覆盖的 Spring Framework、旧 Hibernate Validator、旧 Servlet Filter、旧 APM Agent。

Gradle 项目可以使用:

bash
./gradlew dependencies
./gradlew dependencyInsight --dependency spring-core

八、双版本可运行 Demo

下面两个 Demo 提供相同的订单查询接口,便于直接观察版本差异。不要把两个版本的源码和依赖混在同一个模块中;真实迁移应使用两个分支或独立验证模块。

8.1 JDK 8 + Spring Boot 2.7.18

pom.xml

xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>order-boot2-demo</artifactId>
    <version>1.0.0</version>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Boot 2 DTO 使用 javax.validation

java
package com.example.order;

import javax.validation.constraints.NotBlank;

public class CreateOrderRequest {
    @NotBlank
    private String productCode;

    public String getProductCode() {
        return productCode;
    }

    public void setProductCode(String productCode) {
        this.productCode = productCode;
    }
}

8.2 Java 17 + Spring Boot 3.x

示例中的 Boot 版本应替换为团队当前验证并受支持的精确 3.x 版本,不能把文档示例当作永久版本矩阵:

xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.13</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>order-boot3-demo</artifactId>
    <version>1.0.0</version>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Java 17 启动类:

java
package com.example.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

Boot 3 DTO 使用 jakarta.validation,这里使用 Record 表达不可变输入:

java
package com.example.order;

import jakarta.validation.constraints.NotBlank;

public record CreateOrderRequest(
        @NotBlank(message = "productCode不能为空") String productCode) {
}

Controller:

java
package com.example.order;

import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public OrderResponse create(@Valid @RequestBody CreateOrderRequest request) {
        return new OrderResponse(UUID.randomUUID().toString(), request.productCode(), "CREATED");
    }

    public record OrderResponse(String orderId, String productCode, String status) {
    }
}

运行与验证:

bash
mvn clean verify
mvn spring-boot:run
curl -i -X POST http://localhost:8080/api/orders \
  -H "Content-Type: application/json" \
  -d '{"productCode":"SKU-1001"}'

productCode 应返回 400;正常输入应返回 201。只验证 200/201 不够,迁移测试还应覆盖校验失败、JSON 字段错误、异常转换和安全过滤器。

九、迁移第一阶段:建立可证明的依赖与行为清单

升级前先回答以下问题:

  • 当前生产实际运行 JDK 是多少,而不是开发电脑是多少?
  • Boot、Spring Framework、Cloud、Cloud Alibaba 的精确版本是什么?
  • 哪些公共 Starter、Filter、Interceptor、JPA Entity、Validator 使用 javax
  • 是否依赖 Sleuth、旧 Security 配置、旧 Hibernate、自定义序列化器?
  • 构建插件、测试插件、代码生成器和 APM Agent 是否支持 Java 17?
  • 当前 API、消息、数据库行为基线是什么?

建议保存以下证据:

bash
java -version
mvn -version
mvn dependency:tree > dependency-tree-before.txt
mvn help:effective-pom > effective-pom-before.xml

再建立契约测试:HTTP 状态码与 JSON、MQ 事件 Schema、数据库写入、事务回滚、权限规则、定时任务、序列化兼容。没有行为基线,升级后“能启动”也无法证明业务没变。

十、Boot 2.7 到 Boot 3.x 的可回滚迁移流程

mermaid
flowchart TD
    A["固定当前行为基线"] --> B["先升级到Boot 2.7最新维护基线"]
    B --> C["清理废弃API和手工覆盖依赖"]
    C --> D["切换构建与运行JDK到17"]
    D --> E["升级Boot 3与Spring 6兼容依赖"]
    E --> F["迁移javax到jakarta及框架API"]
    F --> G["运行单测、集成测试和契约测试"]
    G --> H["预发布压测与可观测性对比"]
    H --> I["小流量灰度并保留回滚版本"]

10.1 为什么建议先站到 Boot 2.7

从更老版本直接跨越多个代际,会把配置变化、废弃 API、依赖升级和 Jakarta 迁移混在一次提交里。先迁到 2.7 可以消除一部分旧债,并利用 2.7 对新自动配置机制等过渡能力的支持。

10.2 为什么先让应用在 Java 17 上跑,再切 Boot 3

Boot 2.7 可以运行在 Java 17 上。先只改变运行时,有助于把 JDK 变化和框架变化拆成两个可观察步骤。若同时改变 JDK、Boot、Cloud、ORM 和容器,异常很难归因。

10.3 使用迁移报告和自动化工具,但不能盲信

Spring Boot 提供 properties migrator 帮助发现一部分重命名或删除的配置;OpenRewrite 等工具可以机械迁移常见 API。工具只能减少重复劳动,不能验证权限语义、事务边界、SQL、消息兼容和线上容量。

10.4 配置项如何迁移

  1. 启动时收集废弃与未绑定配置警告。
  2. 对比 Boot 2 和目标 Boot 3 的配置元数据。
  3. 检查 Actuator /env/configprops/conditions
  4. 注意端点暴露和脱敏,不能把密码公开给生产网络。
  5. 删除 migrator 前重新跑一次全量测试,避免长期依赖兼容层。

十一、微服务升级不能只升级单个应用

11.1 Spring Cloud 必须按发布列车匹配

Spring Cloud 不是随便选一个版本号。每个 Release Train 对应一组受支持的 Boot 版本。若 Boot 3 升级后仍保留不兼容的旧 Cloud,可能出现自动配置类缺失、方法签名错误、负载均衡器或配置加载异常。

11.2 Spring Cloud Alibaba 也有第三层矩阵

必须同时对齐:

text
Java → Spring Boot → Spring Cloud Release Train → Spring Cloud Alibaba → Nacos/Sentinel/Seata等客户端

不能只把 Nacos Starter 的版本改大。配置加载方式、服务发现模型、负载均衡、Sentinel 适配和 Seata 数据源代理都可能受影响。

11.3 服务可以分批升级吗

通常可以,因为 HTTP、RPC、MQ 和数据库协议跨 JVM 版本。但必须保证:

  • HTTP 契约和 JSON 字段兼容。
  • Dubbo 等 RPC 协议、序列化和接口模型兼容。
  • 消息消费者能处理新旧 Schema。
  • Trace 传播头在新旧观测体系间可衔接。
  • 注册中心元数据和健康检查语义一致。
  • 回滚时数据库 Schema 仍向后兼容。

先升级边缘、低风险、依赖少的服务,再处理核心交易链;也可以先升级公共基础设施验证服务,但不要让未经验证的新公共 Starter 一次污染所有服务。

十二、商业系统验收:不只是“构建成功”

验收层必测内容证据
构建Java版本、依赖收敛、重复类CI日志、dependency tree
启动Profile、配置绑定、自动配置、端口启动日志、conditions
WebFilter、校验、JSON、上传、异常MockMvc/真实HTTP测试
安全登录、授权、CSRF/CORS、拒绝响应安全契约测试
数据SQL、事务、锁、ORM映射集成测试和数据库证据
消息序列化、重复、顺序、重试、死信Broker与消费日志
微服务注册、发现、负载、超时、熔断Trace和故障注入
性能吞吐、P95/P99、CPU、Heap、GC对照压测报告
运维健康探针、优雅停机、Dump、回滚发布演练记录

十三、升级失败的证据化排查 Runbook

13.1 第一步:确认真正使用的 Java

bash
java -version
mvn -version
java -XshowSettings:properties -version

容器中还应执行同样命令,不要用宿主机结果替代容器结果。检查镜像 tag 之外,还要看镜像 digest 和进程实际二进制。

13.2 UnsupportedClassVersionError

含义:运行 JVM 不支持某个 class 的 major version。

排查:确定报错 class 来自业务还是依赖;检查 CI 编译 JDK、Maven Compiler release、运行镜像。解决方式是升级运行 JVM,或按目标版本重新编译;Boot 3 本身不能降到 Java 8 class 级别运行。

13.3 ClassNotFoundException: javax...

含义:仍有代码或 Jar 依赖旧 Jakarta EE 前身类型。

排查:

bash
mvn dependency:tree
jdeps --multi-release 17 your-library.jar

再检查异常栈中第一个业务或第三方类。不要简单补一个旧 javax.servlet-api 到 Boot 3 中让两套 Servlet API 共存,这通常会把问题推迟成更隐蔽的类型冲突。

13.4 NoSuchMethodErrorAbstractMethodError

含义:编译时看到的方法与运行时实际加载的类不一致,典型原因是依赖冲突或旧扩展实现不兼容新接口。

排查:用 dependency:tree -Dverbose 找版本覆盖;用 -verbose:class 或 Java 9+ 的 -Xlog:class+load=info 查看类从哪个 Jar 加载;恢复 Boot BOM 管理并升级第三方扩展。

13.5 应用启动但接口 404/500

404 要检查组件扫描、路径匹配、Context Path 和 Security;500 要沿异常链检查 Filter、参数校验、Jackson、ORM 和业务调用。迁移后 javax.validation 注解若没有进入新的校验体系,可能出现“没有报错但校验没生效”,因此必须有非法输入测试。

13.6 Actuator 或 Trace 消失

先检查 Actuator 依赖与端点暴露,再检查 Micrometer Observation/Tracing bridge、Exporter、采样率和传播格式。不要因为日志中有 traceId 就认定 Trace 已成功上报。

13.7 启动内存和延迟变化

对比同等流量、相同容器限制下的 CPU、RSS、Heap、Metaspace、GC、线程数、连接池和 P99。升级 JDK 后默认 GC 或容器参数可能不同;只比较平均响应时间会掩盖尾延迟。

十四、常见错误,以及“不这样会怎样”

错误为什么错后果
只替换 javax 字符串第三方字节码和容器规范不会随源码替换启动或请求期类加载失败
Boot 升 3,Cloud 版本不动发布列车不兼容自动配置或调用链运行失败
手工指定所有 Spring 版本破坏 BOM 约束NoSuchMethodError、行为漂移
把所有 Boot 3 能力当成 3.0 就有能力来自不同 Framework/Boot 小版本示例无法编译或配置无效
把虚拟线程说成 Java 17虚拟线程在 Java 21 正式提供技术选型和容量评估错误
迁移只测启动请求路径才会触发懒加载组件和业务语义上线后才暴露 Filter、校验或权限问题
在一个发布中改框架、数据库和业务规则故障无法归因回滚困难、风险成倍增加
新旧服务共用破坏性数据库变更老版本无法读新结构灰度和回滚失败

十五、面试标准回答

知识点页负责解释原理,面试页负责组织标准回答和追问,避免读者只背结论。请进入独立的 Java 17 与 Spring Boot 3.x 面试题。其中每道题都能跳回本页或专题页的精确原理锚点。

十六、关联知识点

本章小结

Java 17+ 与 Spring Boot 3.x 不是把版本数字改大。它是一条从 JVM 字节码基线、Spring Framework 6、Jakarta 类型体系、Servlet 容器、自动配置、Security、Observability、AOT,一直延伸到 Spring Cloud 和第三方生态的系统升级链。

真正掌握它,要能同时维护两种现实:理解 JDK 8 + Boot 2.7 存量系统为何仍能工作,也能为新项目正确选择 Java 17/21 和匹配的 Boot 3.x;遇到失败时能从 class 版本、包名、依赖树、自动配置报告、请求契约和生产指标中取得证据,而不是靠“多换几个版本试试”。