版本号从 3 跳到 4,底层 Framework 从 6 跳到 7。
这不是一次小修小补,而是把整个地基都换了。
先问一个问题:你用的 Spring Boot 是啥版本?
如果你还在 3.x 甚至 2.x 上摸鱼,那这篇文章得好好看看。
事情是这样的。上个月有个兄弟问我:"Spring Boot 4 出了,要不要升?"我说你先别急,让我看看它到底改了啥再说。
结果一看,好家伙——这特么不是加几个新注解的升级,这是把房子拆了重盖。
Spring Boot 4.0 是 2025 年 11 月 30 号发布的,底层是 Spring Framework 7。到现在最新稳定版已经是 4.1.0(2026 年 6 月)。与此同时,Spring Boot 3.5.x 在 2026 年 6 月 30 号 OSS 支持已经正式结束了。
Spring 团队的意思很明确:别在 3.x 上磨蹭了,赶紧过来。
先捋一下版本号的逻辑。
Spring Boot 3.x 底层是 Spring Framework 6(2022 年底发布),Spring Boot 4.x 底层是 Spring Framework 7(2025 年底发布)。
从 6 到 7 这跳得有多大?这么说吧:
| 对比项 | Spring Boot 3.x | Spring Boot 4.x |
|---|---|---|
| Java 基线 | Java 17 | Java 17 最低,推荐 Java 21/25 |
| Spring Framework | 6.x | 7.x |
| 嵌入式服务器 | Tomcat 10 / Undertow / Jetty | Tomcat 11+ / Jetty(Undertow 滚了) |
| Jackson | 2.x(com.fasterxml) | 3.x(tools.jackson) |
| Hibernate | 6 | 7 |
| Jakarta EE | 10 | 11 |
| Servlet | 6.0 | 6.1 |
| Micrometer | 1.x | 2.x |
| 模块数 | 1个大jar | 70+ 独立模块 |
看到没有?从 Hibernate 到 Jackson 到 Servlet 到嵌入式服务器,全线换新。
这就好比你买车——3.x 是给你换了新发动机,4.x 是连底盘、变速箱、轮毂、车机全换了。
如果你项目用的嵌入式服务器是 Undertow,那你是逃不掉了——Undertow 不支持 Servlet 6.1,直接被 Spring 7 踢出局。
得切到 Tomcat 11+ 或者 Jetty。Tomcat 11 是官方推荐方案。
com.fasterxml.jackson 变成了 tools.jackson。
所有 import 要改。所有自定义序列化器、反序列化器要重写。Jackson 3 的配置模型改成不可变的了,以前用 ObjectMapper 各种 setter 配置的方式也得跟着变。
这可能是 3.x 升 4.x 过程中最费时的部分,没有之一。
同样不是小版本升级。Hibernate 7 去掉了一堆废弃 API,语义查询那块改动也比较大。如果你项目里有 Hibernate 自定义类型、Interceptor、EventListener,报错大概率出现在这里。
这个后面细聊,但先提一嘴——之前做重试、限流得引入 Resilience4j,现在 Spring 内置了,所以 Resilience4j 不再是必选项了。但你要继续用也可以,Spring 7 还是兼容的。
淦。你以为 NullPointerException 这玩意儿还只是在运行时才炸?
Spring 3.x 的时候,空安全全靠各家野路子——Spring 自己搞了 org.springframework.lang.Nullable,JetBrains 搞了 org.jetbrains.annotations.Nullable,Lombok 又来一套。IDE 支持是有的,但静态分析一塌糊涂,根本没法统一。
Spring 4 直接一锤定音:全换成 JSpecify(org.jspecify.annotations)。
用法很简单,在 package-info.java 里写一行:
java@NullMarked
package com.example.myapp;
然后这个包里所有方法的参数、返回值默认就是 non-null。只有显式标了 @Nullable 的才允许为空。
javapublic User findById(Long id) {
// id 不能是 null,编译器会检查
return userRepository.findById(id).orElseThrow();
}
// 允许为空的地方要显式声明
public List<User> search(@Nullable String name) {
if (name == null) {
return userRepository.findAll();
}
return userRepository.findByName(name);
}
也就是说,以前你在 IDE 里看到的灰色警告,现在直接变成编译错误。配合 NullAway 这种静态分析工具,可以在 CI 阶段就卡住 NPE,而不是等跑到线上才炸。
对 Kotlin 用户来说也是利好——Spring 的 nullability 信息传过去后,Kotlin 不用再到处写 !! 了。
说人话就是:以前写代码像闭着眼睛走夜路,现在给你发了把手电筒。
用过 Feign 的兄弟都懂,声明式 HTTP 客户端确实方便——写个接口,加几个注解,Spring 自动生成实现。
之前 Spring 其实也有 @HttpExchange,但你需要手动配置 HttpServiceProxyFactory:
java// 以前:手动配置一大串
@Bean
public TodoService todoService(RestClient.Builder builder) {
var restClient = builder.baseUrl("https://api.example.com").build();
var adapter = RestClientAdapter.create(restClient);
var factory = HttpServiceProxyFactory.builderFor(adapter).build();
return factory.createClient(TodoService.class);
}
Spring Boot 4 直接干了:@ImportHttpServices,一行注解就够:
java@Configuration
@ImportHttpServices(TodoService.class)
public class HttpClientConfig {
// 没了,就这
}
@HttpExchange(url = "https://jsonplaceholder.typicode.com")
public interface TodoService {
@GetExchange("/todos")
List<Todo> getAllTodos();
@GetExchange("/todos/{id}")
Todo getTodoById(@PathVariable Long id);
@PostExchange("/todos")
Todo createTodo(@RequestBody Todo todo);
}
接口自动变成 Bean,直接 @Autowired 注入就能用。类型安全、编译期检查,不用再手写 RestTemplate 调用。
这玩意儿跟 Feign 比怎么样?功能上差不多,但胜在 零外部依赖——不需要引入 OpenFeign 的依赖和 Spring Cloud 那一套。适合那些不想再堆技术栈的新项目。
这特么是这波更新里最被低估的改动。
Spring Boot 3.x 的启动过程是怎么工作的?Spring 通过反射扫描你的字节码,找到 @Configuration、@Bean、@ConditionalOnClass,然后一个一个解析。所有这些步骤都发生在运行时。
几十上百个条件注解,每个都要加载类、检查类路径、评估表达式——这就是为什么 Spring Boot 应用启动慢的核心原因之一。
Spring 4 推出了 BeanRegistrar:
javapublic class AppRegistrar implements BeanRegistrar {
@Override
public void register(BeanRegistry registry, Environment env) {
String dbType = env.getProperty("app.db-type", "h2");
switch (dbType) {
case "mysql" ->
registry.registerBean("dataSource", MysqlDataSource.class,
spec -> spec.supplier(MysqlDataSource::new));
case "h2" ->
registry.registerBean("dataSource", H2DataSource.class,
spec -> spec.supplier(H2DataSource::new));
}
}
}
看到区别了吗?没有反射、没有字节码扫描、没有 @ConditionalOnClass。Bean 的注册逻辑是在编译期就由 AOT 处理器处理好的,运行时就只管执行。
实际收益:结合模块化改动,一个典型微服务的启动时间可以快 50%-70%。在 K8s 上冷启动时,这不只是体验问题——这他妈是钱的问题。
API 版本管理这个需求,在之前的 Spring 里一直是个灰色地带。
有的团队用 URL 路径 /api/v1/users、/api/v2/users,每个 Controller 写两套方法,@RequestMapping 加正则匹配。
有的团队用自定义 Header,自己写拦截器解析 Accept-Version。
有的团队在 Query Parameter 里传 ?version=1。
结果就是每个项目一套搞法,OpenAPI 文档对不上,前端调用靠猜。
Spring 7 终于把这事儿内置了:
java@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping(version = "1.0")
public UserDTOv1 getUserV1(@PathVariable Long id) {
return new UserDTOv1(id, user.getName());
}
@GetMapping(version = "2.0")
public UserDTOv2 getUserV2(@PathVariable Long id) {
return new UserDTOv2(id, user.getFirstName(), user.getLastName());
}
}
支持三种策略:
Accept: application/json;version=1.0/api/v1/users/api/users?version=1还支持 RFC 兼容的 Deprecation Header,通知客户端某个版本什么时候下线。
java@Configuration
public class ApiVersioningConfig implements WebMvcConfigurer {
@Override
public void configureApiVersioning(ApiVersionConfigurer configurer) {
configurer
.useMediaTypeParameterVersioning()
.deprecateVersion("1.0", "2026-12-31", "Use version 2.0 instead");
}
}
说人话就是:不用再自己写拦截器了,框架帮你干了。
这是做微服务的兄弟们最该关注的更新。
之前想在 Spring 里做重试、限流、熔断,得搞 Resilience4j。这东西本身是好东西,但有个老大难问题——异步重试时上下文丢失。
Resilience4j 的重试在单独的线程池里跑,SecurityContext、TraceContext、MDC 全丢了。每个集成 Spring Cloud 的项目都在踩这个坑。
Spring 7 直接把容错能力内置了:
@Retryable——原生重试
java@Service
public class PaymentService {
@Retryable(
maxAttempts = 4,
backoff = @Backoff(delay = 500, multiplier = 2.0, jitter = 100)
)
public String fetchData(String id) {
return externalApi.getData(id);
}
}
AOP 代理在同一个线程里重试,Security 上下文、Trace 上下文、MDC 全都在,不会丢。支持指数退避 + jitter,防止惊群效应。
@ConcurrencyLimit——并发控制
java@Service
public class ReportService {
@ConcurrencyLimit(2) // 最多 2 个并发
public String performHeavyOperation(String taskId) {
return heavyComputation(taskId);
}
}
不需要引入外部限流库,一个注解搞定。
不过也要说个大实话:内置的只是重试和并发控制,没有完整的熔断器和舱壁隔离。如果你需要 Circuit Breaker、Bulkhead 这些高级特性,Resilience4j 仍然是你最好的选择。但日常 80% 的微服务场景,重试 + 限流已经够用了。
这块有点底层,但对性能影响很大。
Spring Data JPA 的核心理念是"方法名解析成 SQL"——你写个 findByNameContainingIgnoreCase,它给你拼出 WHERE name LIKE %?%。
这个过程在 Spring 3.x 里是在运行时完成的。每次项目启动,Spring Data 都要解析所有 Repository 接口的方法名,生成查询,然后缓存起来。
Spring Data AOT 把这个过程搬到了编译期:
javapublic interface CoffeeRepository extends CrudRepository<Coffee, Long> {
// AOT 编译期生成 SQL
List<Coffee> findByNameContainingIgnoreCase(String name);
List<Coffee> findByRoastLevelAndOrigin(String roastLevel, String origin);
@Query("SELECT * FROM coffee WHERE price < :maxPrice ORDER BY price")
List<Coffee> findAffordableCoffees(BigDecimal maxPrice);
}
编译时,AOT 处理器会解析方法名、生成对应的 SQL 源码,放在 target/spring-aot/ 目录下。启动时直接加载预生成代码,省掉了方法名解析和查询构建的时间。
给你的项目带来的收益:
可观测性这块,Spring 之前一直靠 Micrometer + 手动配置 Exporter。
3.x 的 Micrometer Tracing 已经有了基本能力,但要把 Trace、Metrics、Logs 打通,还是得自己去搭 OpenTelemetry Collector,配置 Exporter,写一堆 config。
Spring Boot 4 直接给了 spring-boot-starter-opentelemetry:
xml<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-opentelemetry</artifactId>
</dependency>
配置也很简单:
yamlspring:
application:
name: my-service
management:
tracing:
sampling:
probability: 1.0
otlp:
tracing:
endpoint: http://localhost:4318/v1/traces
搞定后,HTTP 请求、数据库调用、日志全部自动关联 Trace ID:
2026-07-22 10:30:45 INFO [traceId=abc123, spanId=def456] Processing order...
要加自定义 Span 的话,用 @Observed:
java@Service
public class OrderService {
@Observed(name = "order.process", contextualName = "processOrder")
public Order processOrder(OrderRequest request) {
return orderRepository.save(createOrder(request));
}
}
跟之前比,最大的区别是不用自己搭 OTel Collector,直接 OTLP 协议打到后端(Jaeger、Grafana Tempo、SigNoz 都支持),一套配置全自动。
做 Spring 测试的老哥应该都有这个经历:
MockMvcTestRestTemplateWebTestClient三个 API,三种风格,脑子都要换三次。
Spring Boot 4 的 RestTestClient 一统江湖:
java// 单元测试——绑定 Controller(快速、隔离)
@ExtendWith(MockitoExtension.class)
class TodoControllerTest {
private RestTestClient client;
@BeforeEach
void setUp() {
client = RestTestClient.bindToController(
new TodoController(todoService)
).build();
}
@Test
void shouldGetAllTodos() {
when(todoService.findAll())
.thenReturn(List.of(new Todo(1L, "Learn Spring", false)));
client.get()
.uri("/api/todos")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$[0].title").isEqualTo("Learn Spring");
}
}
java// E2E 测试——绑定真实服务器
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class TodoIntegrationTest {
private RestTestClient client;
@BeforeEach
void setUp() {
client = RestTestClient.bindToServer()
.baseUrl("http://localhost:" + port)
.build();
}
@Test
void shouldCreateTodo() {
client.post()
.uri("/api/todos")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(new Todo(null, "Integration test", false))
.exchange()
.expectStatus().isCreated();
}
}
支持 5 种绑定方式:bindToController、bindToMockMvc、bindToApplicationContext、bindToServer、bindToRouterFunction。
说人话就是:以后写测试不用记三套 API 了,一套搞定从单元到 E2E。
如果你项目里用到了 JMS(ActiveMQ Artemis 之类的),以前用 JmsTemplate 发消息是这样:
javajmsTemplate.convertAndSend("orders.queue", order);
新版 JMS Client API 采用了 Fluent 风格:
java@Service
public class OrderMessagingService {
private final JmsClient jmsClient;
// 基础发送
public void sendOrder(Order order) {
jmsClient.send("orders.queue").withBody(order);
}
// 带优先级和 TTL
public void sendPriorityOrder(Order order) {
jmsClient.send("orders.queue")
.withPriority(9)
.withTimeToLive(Duration.ofMinutes(5))
.withBody(order);
}
// Request-Reply 模式
public OrderConfirmation processOrder(Order order) {
return jmsClient.requestAndReceive("orders.queue")
.withBody(order)
.convertTo(OrderConfirmation.class);
}
}
清爽很多,而且支持 7 种消息模式。不过 JmsTemplate 仍然保留,不强制迁移。
这是架构级别的改动,你可能写代码时感受不到,但它带来的收益是实打实的。
Spring Boot 3.x:spring-boot-autoconfigure 一个大 Jar 里装了所有东西——JDBC、Kafka、Redis、MongoDB、WebSocket……哪怕你只用了数据库,Maven 还是把所有 autoconfigure 类都拉到 classpath 里。
Spring Boot 4.x:拆成 70+ 独立模块:
spring-boot-autoconfigure-jdbc spring-boot-autoconfigure-kafka spring-boot-autoconfigure-redis spring-boot-autoconfigure-web-servlet spring-boot-autoconfigure-webflux spring-boot-autoconfigure-mongodb ...
你声明什么 Starter,就只拉对应的 autoconfigure 模块。
直接收益:
@ConditionalOnClass 评估在 K8s 上跑微服务的兄弟都懂——冷启动时间每少一秒,扩缩容的体验就好一个档次。
说了这么多好东西,也该说说升级要踩的坑了。毕竟从 3.x 到 4.x,跟之前 2.x 到 3.x 一样,不是加个版本号就能跑的。
第一名:Jackson 2 → 3
com.fasterxml.jackson 变成 tools.jacksonObjectMapper 的 setter 要改第二名:Undertow 移除
第三名:Hibernate 6 → 7
第四名:Spring Security 6 → 7
第五名:配置属性重命名
management.tracing.enabled → management.tracing.export.enabledspring.dao.exceptiontranslation.enabled → spring.persistence.exceptiontranslation.enabled还在 Boot 2.x? → 先升 3.5(清掉所有 deprecation) → 再升 4.x 已在 Boot 3.x? → 升到最新 3.5.x(清 deprecation) → 升 4.x 新项目? → 直接 4.1.0,别犹豫
官方说了:不要跨版本跳。3.3.x 直跳 4.x 的方式,编译可能通过但运行时一堆问题。
别想着周末加个班就能搞定。一个中等规模的微服务(几十个模块、几百个 API),保守估计:
| 阶段 | 时间 |
|---|---|
| 依赖分析 + 影响评估 | 1-2 天 |
| Jackson 3 迁移 | 1-3 天 |
| Hibernate 7 适配 | 1-2 天 |
| Security 配置更新 | 1 天 |
| 测试 + 修复 | 2-5 天 |
| 灰度发布 | 1-2 天 |
加起来 1-2 周,还是在测试覆盖率高、没有太多自定义配置的前提下。
但话说回来,从 3.x 升 4.x 的收益,对于做微服务 + K8s 的团队来说,绝对值这个时间。
前面聊的都是 4.0 的基础功能。4.1.0(2026年6月发布)在此基础上再加了 5 个东西:
写 gRPC 服务器和客户端不再需要自己搞 Protobuf + Netty 那一套。支持 Netty 独立部署或通过 Servlet over HTTP/2 集成。
InetAddressFilter 可以配置黑名单/白名单,限制出站请求能连的 IP 段,防止 SSRF 攻击。
yamlspring:
http:
client:
ssrf:
inet-address-filter:
allowed: "10.0.0.0/8"
blocked: "169.254.0.0/16"
@Async 方法现在自动传播追踪上下文,不再跨线程丢 Trace 了。
Log4j 支持可配置的轮转策略:大小、时间、大小+时间、Cron 表达式。Logback 那边之前早就有轮转配置,Log4j 用户终于等到了。
基于 Spring Boot 4.0 基线,引入 MCP SDK 2.0、@McpTool 注解模式、Agentic 改进。想搞 AI Agent 的兄弟可以关注。
Spring Boot 4 不只是一家升,整个 Spring 生态全系联动:
| 项目 | 3.x 对应版本 | 4.x 对应版本 |
|---|---|---|
| Spring Data | 2024.x | 2025.1 / 2026.0 |
| Spring Security | 6.x | 7.x |
| Spring Kafka | 3.x | 4.x |
| Spring Integration | 6.x | 7.x |
| Spring AI | 1.x | 2.0 |
| Spring Modulith | 1.x | 2.x |
| Spring Batch | 5.x | 6.x |
| Spring AMQP | 3.x | 4.x |
| Spring Session | 3.x | 4.x |
| Spring Cloud | 2024.x | 待确认,建议升前查兼容表 |
如果你的项目依赖 Spring Cloud Alibaba(就像咱们的 nsw-project 一样),等 Spring Cloud Alibaba 出了对应 4.x 的版本再升,不要独自冲锋。
如果你问我个人的看法——分情况。
新项目:直接 Spring Boot 4.1.0 + Java 21。没任何理由在新项目上还用 3.x。启动快、内置容错、原生 OTel、API 版本控制,这些都是白捡的优势。
已有 Boot 3.x 项目:等你的迭代节奏到了大版本窗口期再升。先把当前版本的 deprecation 清干净,升到 3.5.x,再跳 4.x。优先确保 Spring Cloud Alibaba 的兼容性。
还在 Boot 2.x 的老项目:你有两个 migration 要走,别想着一步到位。先 2→3,再 3→4,每一步都要走稳。建议先在内网工具/低流量服务上试点,练熟了再动核心服务。
不升级的代价:Boot 3.5.x 的 OSS 支持在 2026 年 6 月 30 号已经结束。也就是说,不会再收到安全补丁和 bug 修复。如果你还在生产环境跑 3.5 以下的版本,要么升,要么买商业支持。安全漏洞不会等你准备好才来。
Spring Boot 4 这波更新,不是那种"加了几个注解、性能优化 10%"的小升版。它是把 Spring 从一个"运行时要反射扫描、启动要半分钟、容错靠外部依赖"的传统框架,往"编译期搞定、启动秒级、原生支持云原生"的方向彻底转型。
核心变化就这几句话:
最实在的忠告:依赖清洗是这次升级最痛的部分,先评估影响面,再排计划,不要盲目开搞。
这玩意儿属于"早搞早享受,但不做好功课硬搞就是找虐"的典型。
还没细看的兄弟建议找个周末,建个分支跑一遍编译再决定。体验提升是真的,代价也是真的。
本文基于 Spring 官方 Release Notes、Dan Vega 博客、多位技术博主分析以及 Spring 4.1 发布公告整理。 有用的话点个赞,有问题下面留言。


本文作者:JACK WEI
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!