
Spring Boot 4.0 都快发布一年了,你们都上车了吗?
Spring Boot 4.0 都快发布一年了,你们都上车了吗?
1、引言
作为一名 CRUD 日常拉满的 Java 后端,每天都会和 Spring Boot 打交道,最近刷社区才发现:Spring Boot 4.0 都快发布一年了,我竟然还在用 3.x!不知道有没有落伍,今天咱们就来研究研究 4.0 到底怎么个事儿!
先喂一颗定心丸:这次不像 Boot 2 升 Boot 3 那种“换爹级”大迁移,当年 javax 包全改成 jakarta,多少人周末通宵改代码,骂骂咧咧。Boot 3 升级 Boot 4 没有这种一刀切的史诗级爆破,属于内功大幅升级、体验持续优化的版本。不过它并不是无痛升级,Jackson、starter模块、底层依赖都有不少破坏性调整,迁移前一定要摸清。

4.0 正式发布于 2025 年 11 月 20 日,底层换成了 Spring Framework 7。接下来先扒一扒它给我们准备了哪些新东西,再聊聊老项目怎么升、值不值得现在升。
2、新版本带来了什么变化
2.1、自动配置拆包了,“全家桶”改成按需点单
用过 Spring Boot 3 的同学都懂,spring-boot-autoconfigure 是个庞然大物,里面装着 Redis、MQ、MongoDB 等各种技术的自动配置类。没用到的技术,相关配置通常不会生效,但这一坨仍会跟着打进 JAR 包里。
Boot 4 直接干了一件大事:把这些自动配置拆成了更小巧、职责单一的小模块。需要什么就引入什么,少带些无关的类,IDE 补全和配置提示也会更清爽。〔2〕
这件事落到我们手上,最直接的变化就是:pom.xml 里有些依赖要换了。
比如 Flyway,以前引入 flyway-core,Boot 就能提供自动配置。现在自动配置进行了调整,可以改用对应的 starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-flyway</artifactId>
</dependency>Web MVC 的 starter 也换了个更明确的名字:
spring-boot-starter-web
→ spring-boot-starter-webmvc
MVC 测试支持:
spring-boot-starter-webmvc-test老的 spring-boot-starter-web 在 4.0 里还留着,但已经弃用。自己封装过 starter 的朋友,还要看看自动配置类的包名有没有变。业务代码一行没动,import 先红一片,也不是没有可能🐶〔2〕
2.2、空指针杀手来了:NPE 从 Runtime 炸弹变成编译期警告
Java 码农的一生之敌,NullPointerException(简称 NPE)必须榜上有名。
最烦的是,有时候看着一个返回 String 的方法,真不知道它到底会不会返回 null。每次都判空,嫌啰嗦;不判,又怕哪天它真给你来一个。
Boot 4、Framework 7 等项目这次采用了 JSpecify,把 API 的可空性标清楚。在 @NullMarked 范围内,普通类型用法默认非空;允许为空的地方,用 @Nullable 明确标出来。
比如自己的业务类也可以这样写:
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class NicknameService {
public String displayName(@Nullable String nickname) {
if (nickname == null || nickname.isBlank()) {
return "匿名用户";
}
return nickname;
}
}这里参数可以为空,返回值不能随便返回 null。如果漏掉前面的空值判断,直接调 nickname.isBlank(),支持 JSpecify 的 IDE 或静态检查工具就会提醒你。
框架 API 也是同样的道理。看官方这张图:响应体 body 可能为空,直接调用 length(),IDE 就会提示风险。〔4〕

⚠️ 注意:IDE 的提示不等于 Java 编译器默认会拦截这段代码。想让空值检查进入构建流程,还得配置 NullAway 这类工具。给老项目升个 Boot 版本,并不会帮你自动把业务代码里的 NPE 全消灭掉。但能在敲代码的时候多提醒一下,总比发到生产了才报错要强。
2.3、原生 API 版本控制,告别自己手写版本路由
做过开放接口的同学,以前做 API 版本控制有多痛苦?方案五花八门:URL 加 v1、请求头带版本号、参数传 version。简单区分版本,用原来的请求映射就能做;麻烦的是后面又加了版本校验、兼容规则、弃用提醒,自己写的判断越来越多,逐渐垒成屎山……
现在 Framework 7 提供了原生 API 版本支持,Boot 4 也配好了对应的自动配置。@RequestMapping 新增了 version 属性,支持从请求头、请求参数等位置读取版本号,再由框架匹配对应接口。〔5〕
比如有个订单接口,旧版返回 status: 1,新版想改成 status: "PAID"。先在配置文件中约定一下从请求头取版本号(也支持 Java 类配置):
# 从请求头中获取版本号,此外还支持:路径、查询参数
spring.mvc.apiversion.use.header=API-Version然后在注解里直接写 version:
@RestController
public class OrderController {
@GetMapping(path = "/orders/{id}", version = "1.0")
public Map<String, Object> getV1(@PathVariable("id") long id) {
return Map.of("id", id, "status", 1);
}
@GetMapping(path = "/orders/{id}", version = "2.0")
public Map<String, Object> getV2(@PathVariable("id") long id) {
return Map.of("id", id, "status", "PAID", "statusText", "已支付");
}
}应用启动在 8080 端口时,可以分别发这两个请求:
curl -H "API-Version: 1.0" http://localhost:8080/orders/42
curl -H "API-Version: 2.0" http://localhost:8080/orders/42按上面代码,响应内容分别是下面这样:
{"id":42,"status":1}{"id":42,"status":"PAID","statusText":"已支付"}怎么样,是不是清爽很多?不用自己再造轮子做版本分发了,对那些对外提供接口、多版本兼容的项目非常友好!
2.4、HTTP 客户端:这个写法,Feign 用户应该很眼熟
在 Boot 4 中顺带提供了一个新玩具:声明式 HTTP 客户端——HTTP Service Client,类似 Feign,但属于官方原生能力,不用额外引入 OpenFeign 依赖,接口定义一下就能调用第三方 HTTP 服务,生态亲儿子,稳定性有保障。
Spring 的 HTTP Service Client 可以把远程调用声明成 Java 接口。比如下面这个物流查询,先假设对方提供了 /shipments/{trackingNo}:
import java.util.Map;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.service.annotation.GetExchange;
public interface ShipmentClient {
@GetExchange("/shipments/{trackingNo}")
Map<String, Object> find(@PathVariable("trackingNo") String trackingNo);
}是不是有点眼熟?不过这套接口声明在 Framework 6 就有了,Boot 3 也能用,别把老朋友当成新同事。
4.0 这一代主要补上了注册、分组和自动配置。项目引入 spring-boot-starter-restclient,再用配置类把接口注册进来:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.service.registry.ImportHttpServices;
@Configuration(proxyBeanMethods = false)
@ImportHttpServices(group = "logistics", types = ShipmentClient.class)
public class HttpClientConfig {
}假设本地另有一个物流服务跑在 8081,地址和超时就这样配:
spring.http.serviceclient.logistics.base-url=http://localhost:8081
spring.http.serviceclient.logistics.connect-timeout=2s
spring.http.serviceclient.logistics.read-timeout=3s配置类被扫描到后,就能像普通 Bean 一样注入 ShipmentClient,调用 find("SF123456")。以后物流地址变了,改配置;物流接口又加了几个,放进同一组,共用客户端配置。〔1〕
以前自己创建代理、逐个注册 Bean 的活儿,框架现在能帮忙做了。新接一个外部服务时值得试试,已经用着 OpenFeign 的倒不用连夜重写。
2.5、Jackson 也换代了,JSON 这关可别忽视
Boot 4 默认用上了 Jackson 3。平时只在 Controller 里返回对象,可能感觉不明显;自己配过序列化规则、写过 JSON 工具类的同学,就得翻翻老代码了。
首先看到的是包名变化:
// Jackson 2
import com.fasterxml.jackson.databind.ObjectMapper;
// Jackson 3
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;
// 这些常用注解没搬家,别顺手全局替换了
import com.fasterxml.jackson.annotation.JsonProperty;JsonMapper 专门处理 JSON,也是 Boot 4 默认自动配置的 mapper。如果以前声明了一个 ObjectMapper Bean 来替换默认配置,现在要明确声明成 JsonMapper。只改 import,原来的配置不一定就能接上。〔2〕
单独使用时,可以通过 builder 创建,再配合熟悉的注解:
public class JsonDemo {
public record OrderView(@JsonProperty("order_id") long id) {}
public static void main(String[] args) {
JsonMapper mapper = JsonMapper.builder().build();
String json = mapper.writeValueAsString(new OrderView(42));
System.out.println(json); // {"order_id":42}
}
}这里使用上面的 Jackson 3 和 JsonProperty 导包。放回 Boot 项目,通常直接注入它配置好的 mapper 就行,不用每次手动创建。
更容易漏的是默认行为变化。编译通过了,前端却发现日期格式不一样,或者 Redis 里的旧 JSON 读不回来了,这才头大。自定义序列化器、日期和缓存数据,记得拿原来的样本过一遍。
迁移初期想尽量沿用旧版默认行为,可以先加:
spring.jackson.use-jackson2-defaults=true这是让 Jackson 3 的默认设置尽量靠近旧版,底层并没有退回 Jackson 2。Boot 4 还提供已弃用的 Jackson 2 兼容支持,能用来过渡,但长远来看还是得迁移。〔2〕
2.6、Java 17 还能用,Java 25 也安排上了
看到 Boot 4,有些人的第一反应可能是:完了,JDK 也得跟着换?
不用!普通 JVM 应用的最低要求仍然是 Java 17。已经用着 Java 17 或 21 的项目,不必为了升 Boot 顺手把 JDK 也换了;想用 Java 25,这次也支持。不过具体特性还得看 JDK,比如虚拟线程至少需要 Java 21,光升框架可不够。
其他环境门槛顺带说一下:Kotlin 至少 2.2,Servlet 基线升到 6.1;如果要构建 GraalVM 原生镜像,则需要 GraalVM 25 或更新版本。另外,Boot 4.0 移除了 Undertow 支持,用它或打 WAR 部署的项目要核对容器。〔2〕
2.7、其他零碎但是很香的小特性
除了前面这些大变化,还有几项写业务时可能用得上,简单划个重点:〔1〕
- 配置报错更好找了。 配置属性绑定因为找不到类而失败时,错误信息更清楚,排查问题时更高效。
- Actuator 的证书检查更细了。 SSL 健康检查会在
expiringChains里列出即将到期的证书链,排查证书续期问题更方便。 - 控制台日志有单独开关了。 配置
logging.console.enabled=false就能关闭控制台日志,需要按部署环境控制输出时省点事。 - 接口测试多了 RestTestClient。 MockMvc 测试和真实服务的集成测试,都能用这套客户端 API 写请求与断言。
- 虚拟线程适配继续补齐。 在 Java 21 及以上版本开启
spring.threads.virtual.enabled=true后,Boot 自动配置的、基于 JDKHttpClient的客户端也会使用虚拟线程。 - OpenTelemetry 有专门的 starter。
spring-boot-starter-opentelemetry帮忙配置 SDK 和 OTLP 导出支持,接监控时能少做些装配工作,采集端地址等仍按环境配置。 - 任务装饰器可以叠加了。 多个
TaskDecoratorBean 能按顺序组合,像 MDC 传递、自定义上下文处理,不必全塞进一个类里。
3、怎么升级
已经在用 3.x 的,官方墙裂建议按这条路径升级:〔2〕
当前 3.x 版本 → 最新可用的 3.5.x → 清理弃用项 → 4.0.x最终目标是 4.1 的,还要接着看对应迁移说明,别只改个版本号就直接往生产发,得听劝。实施时可以按这几步来:
- 先查依赖。 Spring Cloud、自研 starter、中间件 SDK 有没有兼容版本,别改完才发现某个老组件没人维护了。
- 开分支迁移。 调整 starter、包名和 Jackson 配置;旧配置可以借助
spring-boot-properties-migrator排查,处理完记得移除。 - 回归再上线。 鉴权、JSON、数据库、旧缓存和外部接口重点测,灰度和回滚也要提前准备。
4、最后说两句
一圈看下来,Spring Boot 4 不是颠覆性革命,而是一次 “精细化大升级”:拆包瘦身、打击 NPE、补齐接口版本短板、跟上 JDK 前进脚步。不过不建议盲目跟风升级(但也得提上日程了,毕竟 3.5.16 已是 3.5.x 最后一个开源发行版),说白了,企业项目跑得稳、业务又没新要求,谁愿意没事给自己加活?开发改完,测试、运维、上下游都得跟着忙,成本摆在这儿。
✅ 适合考虑升级的场景:
- 新项目从零起步,依赖也支持 4.x,可以直接评估受支持的版本。
- 项目规模可控、测试覆盖完善,或者确实需要 API 版本管理等新能力。
❌ 不建议急着升级的场景:
- 老核心项目盘根错节,测试覆盖率稀碎,还挂着一堆没适配的 starter 和 SDK。
- 需求排满、工期爆炸,连完整回归和回滚都安排不出来。
技术不是追新竞赛,稳定能扛住流量、少出事故才是后端第一生产力,话说你们都升级了吗?🐶

