Spring Boot 4.0 新特性与企业开发变化:Java 21、Jakarta EE 11 与 Jackson 3
前言
如果只把 Spring Boot 4.0 理解成“换一个版本号”,升级时很容易踩坑。它带来的是整条基础链路的变化:Spring Framework、Servlet、Validation、JSON 序列化和测试依赖都可能一起变化。
这篇教程不逐个罗列 Release Notes,而是用当前配套项目跑出真实版本、序列化结果和虚拟线程信息,让读者先看见变化,再理解变化。
开始之前只需要会写 Java 类、能运行 Maven 命令。完成后你会知道:
- Spring Boot 4.0 的企业项目基线是什么。
- Java 21 的 Record 和虚拟线程如何进入实际代码。
- Jakarta EE 11 对包名有什么影响。
- Jackson 3 在接口返回中扮演什么角色。
- 从 3.x 升级到 4.0 时先检查哪些地方。
配套源码:dvsusan/spring-boot4-demo。本系列的完整环境安装步骤放在下一章。
先看本篇要验证的结果
启动项目后,我们会调用三个接口:
GET /api/v1/system/info
GET /api/v1/system/virtual-thread
POST /api/v1/orders
图 1:Spring Boot 4 的变化贯穿 JDK、框架、EE 规范和 JSON 序列化,最终通过示例接口体现。
第一个接口展示运行时版本和激活 Profile,第二个证明任务确实运行在虚拟线程中,第三个展示 Jakarta Validation 与 Jackson 3 的序列化结果。
这篇文章关注“新特性在代码中的落点”,不要求读者重新搭建项目。请先克隆开源仓库:
git clone https://github.com/dvsusan/spring-boot4-demo.git
cd spring-boot4-demoSpring Boot 4.0.3 的核心基线
配套项目固定使用 Spring Boot 4.0.3。从 Maven 依赖管理中可以读到以下版本:
| 组件 | 版本 | 对开发的影响 |
|---|---|---|
| Spring Framework | 7.0.5 | Web、依赖注入、事务等基础能力升级 |
| Jackson | 3.0.4 | JSON 序列化依赖和包名进入新主版本 |
| Jakarta Validation | 3.1.1 | 参数校验继续使用 jakarta.* 包名 |
| Hibernate Validator | 9.0.1.Final | 提供 Validation 3.1 的实现 |
| Embedded Tomcat | 11.0.18 | Servlet 6.1 基线 |
项目根 POM 同时明确设置了 Java 21:
<properties>
<!-- 示例统一以 Java 21 编译,避免不同电脑的默认 JDK 造成结果差异。 -->
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>这里的重点是可重复。Spring Boot 父 POM 自身有默认 Java 版本,但企业项目应该在根 POM 中写清目标版本。否则一台电脑用 JDK 17、一台用 JDK 21,代码可能在一处编译成功、另一处失败。
Spring Boot 4.0 的 Starter 也做了更细的拆分。创建 MVC 应用时,配套项目使用的是:
<dependency>
<!-- Boot 4 新项目使用细分的 MVC Starter。 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>旧教程里常见的 spring-boot-starter-web 仍可能作为兼容入口出现,但 Spring Boot 4.0.3 的依赖说明已经建议新项目改用 spring-boot-starter-webmvc。迁移时不要看到旧依赖还能下载就忽略这条变化。
第 1 步:让项目展示真实运行环境
打开 demo-web/src/main/java/com/susan/training/web/SystemController.java,可以看到:
/**
* 展示当前运行时版本和激活的 Profile。
*/
@GetMapping("/info")
public ApiResponse<SystemInfo> info(
@Value("${spring.application.name:unknown}") String applicationName
) {
return ApiResponse.success(new SystemInfo(
applicationName,
System.getProperty("java.version"),
System.getProperty("java.vendor"),
Arrays.asList(environment.getActiveProfiles())
));
}Environment 是 Spring 提供的环境信息入口,可以读取激活 Profile 和配置属性。@Value 在这里取应用名,冒号后面的 unknown 是属性不存在时的默认值。
在项目根目录执行:
mvn clean package -DskipTests
java -jar demo-web/target/demo-web-1.0.0-SNAPSHOT.jar --server.port=18080另开终端请求:
curl "http://localhost:18080/api/v1/system/info"示例环境返回:
{
"code": "0",
"message": "success",
"data": {
"applicationName": "demo-platform",
"javaVersion": "25.0.3",
"javaVendor": "Eclipse Adoptium",
"activeProfiles": ["dev"]
},
"timestamp": "2026-11-06T02:30:12.571073Z"
}这里的 Java 运行版本可能与你不同。项目编译目标仍然是 Java 21,但使用更高版本的 JDK 也可以运行。教程验证环境使用 Java 25,因此返回值显示 25;这不代表项目要求 Java 25。
第 2 步:用 Record 表达稳定数据
项目大量使用 record。例如 OrderView:
/**
* 订单查询视图,只暴露调用方需要看到的数据。
*/
public record OrderView(
String orderNo,
String customerId,
BigDecimal amount,
OrderStatus status
) {
}Record 适合表达“创建后不应该随意修改”的数据结构。它自动生成构造器、访问器和 equals/hashCode,比手写十几个样板方法更清楚。
但 Record 不是“所有类都应该这样写”。下面这些情况仍然更适合普通类:
- 需要多个可变状态和明确的修改方法。
- 需要继承或复杂的对象生命周期。
- 构造参数需要经过一组有顺序的业务步骤。
Record 的访问器没有 get 前缀,所以调用时写 order.orderNo(),不是 order.getOrderNo()。
第 3 步:让参数校验进入 Jakarta EE 11
创建订单请求使用了三个校验注解:
/**
* 创建订单的 HTTP 请求对象。
*/
public record CreateOrderRequest(
@NotBlank(message = "客户编号不能为空")
String customerId,
@NotNull(message = "订单金额不能为空")
@DecimalMin(value = "0.01", message = "订单金额必须大于 0")
BigDecimal amount
) {
}请求:
curl -i -X POST "http://localhost:18080/api/v1/orders" \
-H "Content-Type: application/json" \
-d '{"customerId":"","amount":0}'预期 HTTP 400,业务码为 INVALID_ARGUMENT。
Spring Boot 4 继续采用 Jakarta EE 命名空间。代码里必须导入:
// Boot 4 使用 Jakarta Validation 命名空间。
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;从旧项目迁移时,如果 IDE 自动补全出 javax.validation.*,说明依赖或源码仍是旧体系。不要同时混用 javax 和 jakarta 注解,容器只会识别其中一套。
第 4 步:观察 Jackson 3 的接口输出
Controller 返回一个 Java Record,浏览器最终看到 JSON:
{
"code": "0",
"message": "success",
"data": {
"orderNo": "SO685E417C3C4F",
"customerId": "C1001",
"amount": 99.0,
"status": "CREATED"
},
"timestamp": "2026-11-06T01:59:50.000Z"
}Java 对象不会自动变成 JSON。Spring MVC 会把转换工作交给 HTTP Converter,Boot 4 的 Web Starter 引入 Jackson 3 相关依赖。
业务代码通常不需要直接 import Jackson 类,因此升级时最容易漏掉的不是 ObjectMapper,而是自定义序列化器、日期格式器、第三方 JSON 工具和测试断言。
升级前可以搜索:
rg 'com.fasterxml.jackson|ObjectMapper|JsonSerializer|JsonDeserializer' .如果项目自定义了 Jackson 2 的类型,需要逐项确认 Jackson 3 的包名和 API。不要只修改依赖版本就认为序列化已经兼容。
第 5 步:在 Java 21 虚拟线程中执行任务
项目提供一个虚拟线程验证接口:
@GetMapping("/virtual-thread")
public ApiResponse<VirtualThreadInfo> virtualThread() throws Exception {
// 每个任务交给一个虚拟线程,适合阻塞型工作负载。
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<VirtualThreadInfo> future = executor.submit(() -> {
Thread.sleep(20);
Thread current = Thread.currentThread();
return new VirtualThreadInfo(current.isVirtual(), current.toString());
});
return ApiResponse.success(future.get());
}
}newVirtualThreadPerTaskExecutor() 会为每个提交任务创建虚拟线程。Thread.isVirtual() 返回 true 时,说明当前任务确实运行在虚拟线程上。
请求:
curl "http://localhost:18080/api/v1/system/virtual-thread"示例返回:
{
"code": "0",
"message": "success",
"data": {
"virtual": true,
"threadName": "VirtualThread[#47]/runnable@ForkJoinPool-1-worker-1"
}
}虚拟线程不是让 CPU 计算变快的开关。它主要降低大量阻塞任务的线程成本。数据库连接池、下游限流和 CPU 核数仍然是容量上限。
Spring Boot 4 升级时先检查什么?
| 检查项 | 原因 | 建议动作 |
|---|---|---|
| Java 与构建插件 | 编译目标必须一致 | 固定 java.version,同步 CI JDK |
| Starter 名称 | Boot 4 做了模块细分 | MVC 项目检查 spring-boot-starter-webmvc |
javax 到 jakarta | EE 11 仍使用 Jakarta 命名空间 | 清理旧 import 和旧依赖 |
| Jackson 2 到 3 | 自定义序列化代码可能不兼容 | 先跑序列化测试再迁移 |
| 内嵌 Web 容器 | Undertow 不再是可直接沿用的选择 | 迁移前切到 Tomcat 或 Jetty |
| 测试依赖 | 包名和自动配置测试工具可能变化 | 让集成测试真实启动 |
升级时先写一组“现在能通过”的行为测试,再改依赖。否则编译通过后出现返回格式变化、参数校验失效或请求映射消失,很难判断是哪一步造成的。
常见问题排查
| 现象 | 原因 | 处理方式 |
|---|---|---|
javax.validation 找不到 | 仍使用旧 Java EE 包名 | 全部改为 jakarta.validation |
| 测试找不到 MVC 自动配置 | 测试依赖仍是旧组合 | 使用 Boot 4 对应的测试 Starter |
| 自定义 JSON 字段失效 | Jackson 2 注解或序列化器未迁移 | 搜索 Jackson import 并补序列化测试 |
| 启动后 404 | 启动类包范围不正确 | 启动类放在根业务包下 |
虚拟线程返回 false | 任务没有提交到虚拟线程执行器 | 检查 Executor 类型和实际执行路径 |
练习题
给 SystemInfo 增加 springBootVersion 字段,让接口显示当前 Spring Boot 版本。
验收标准:
- 不把版本号写死在 Controller 中。
- 编译通过。
- 请求
/api/v1/system/info能看到版本字段。 - 在代码中添加一条注释,说明版本来源。
总结
Spring Boot 4.0 的变化不只在框架 API,还体现在整个基础依赖链。本文通过 Record、Jakarta Validation、Jackson 3 和虚拟线程,把这些变化落到了真实接口上。
下一篇先完成开发环境检查,确保 JDK、Maven、IDE 和端口配置一致,再进入完整多模块工程搭建。
13 个企业级实战项目 · 源码 + 教程 + 答疑
简历包装指导AI Agent 实战高并发·微服务Offer 导向持续更新3000+ 同行交流1V1 答疑3 天包退
覆盖高并发、微服务、分布式与 AI Agent:100万QPS短链、企业智能知识库、商城微服务、SaaS点餐、秒杀、刷题小程序、代码生成器、AI Agent 等。券后 ¥129(原价 ¥159),不满意 3 天包退。
