RestTemplate 即将挂上 @Deprecated,Spring Boot 4 的远程调用该迁往哪条路?
“Spring Boot 调用外部接口有哪几种方式”几乎是每个 Java 后端都背过的题,标准答案通常是 RestTemplate、WebClient,再加上 OpenFeign 或者 HTTP Interface。这道题的答案在 2026 年发生了实质变化:Spring 官方已经宣布弃用 RestTemplate,按计划在 Spring Framework 7.1 正式打上 @Deprecated,并在 8.0 删除。
这意味着新项目不该再从 RestTemplate 起步,老项目也需要一份迁移路线。本文梳理 Spring 官方给出的时间表和替代方案,对比 RestTemplate、RestClient、@HttpExchange 声明式接口和 WebClient 四种写法,并用 Spring Boot 4.1.1 写一个可运行的对照工程,把超时、错误处理和 API 版本头这几个迁移时最容易出问题的点实际跑一遍。
一、时间表:RestTemplate 的退场计划
2025 年 9 月,Spring 团队在官方博客《The state of HTTP clients in Spring》中正式说明了 RestTemplate 的去留。核心计划只有三步:
| 版本 | 时间 | 对 RestTemplate 的处理 |
|---|---|---|
| Spring Framework 7.0 | 2025 年 11 月 | 在博客、升级指南和参考文档中宣布弃用意图 |
| Spring Framework 7.1 | 暂定 2026 年 11 月 | 正式标注 @Deprecated 并标记为待删除 |
| Spring Framework 8.0 | 待定 | 彻底移除 RestTemplate |
官方同时给了一个让人安心的承诺:按目前的发布节奏,RestTemplate 的开源支持至少会持续到 2029 年。所以这不是“明天就得全部重写”的紧急事件,而是一个可以从容规划的迁移窗口。
为什么要弃用一个用了十几年的组件?官方的解释是:RestTemplate 随 Spring Framework 3.0 发布,至今已超过 15 年,它基于“模板方法”的 API 设计已经无法承载新特性。早在 WebClient 推出时,RestTemplate 就被宣布“功能冻结”,此后 Spring 7 新增的 API 版本化、HTTP 接口客户端分组、客户端侧 Jackson 提示等能力,都只在 RestClient 和 WebClient 上提供。继续使用 RestTemplate,等于主动放弃这些新能力。
⚠️ 同一篇博客还提到,Spring Boot 的
TestRestTemplate也在考虑弃用之列,替代品是 Spring Framework 7.0 新增的RestTestClient。写集成测试的团队也要一并留意。
二、RestClient:同一套底层,换一套 API
RestClient 在 Spring Framework 6.1 引入,它是官方指定的 RestTemplate 接班人。理解它最重要的一点是:RestClient 和 RestTemplate 共用同一套底层 HTTP 库和消息转换器,变的只是调用风格。这让迁移成本大幅降低:连接池配置、ClientHttpRequestFactory、拦截器、Jackson 转换规则都能沿用。

对比一下同一个请求的两种写法。RestTemplate 是一组“动词 + 返回类型”的重载方法:
RestTemplate rt = new RestTemplate(requestFactory);
User u = rt.getForObject(baseUrl + "/api/users/{id}", User.class, 7);
RestClient 则是链式的流式 API,请求的每个要素都有明确的位置:
RestClient client = RestClient.builder()
.baseUrl(baseUrl)
.requestFactory(requestFactory)
.defaultApiVersion("1.2")
.apiVersionInserter(ApiVersionInserter.useHeader("API-Version"))
.build();
User u = client.get().uri("/api/users/{id}", 8).retrieve().body(User.class);
上面的 defaultApiVersion 和 apiVersionInserter 是 Spring Framework 7 的新特性:客户端会自动把 API 版本号写进请求头(也可以写进查询参数、路径段或媒体类型参数),服务端用 @GetMapping(version = "1.2") 之类的写法按版本路由。这类能力 RestTemplate 永远不会有。
错误处理也更直观。RestTemplate 遇到 4xx/5xx 会直接抛 HttpClientErrorException 或 HttpServerErrorException,想自定义要实现一个全局的 ResponseErrorHandler;RestClient 可以在单次调用上用 onStatus 把特定状态码翻译成业务异常,作用域更小,也更不容易误伤其他调用。
三、@HttpExchange:把 Feign 的写法收进框架
如果你习惯 OpenFeign “只写接口、不写实现”的风格,Spring 6 起内置的 HTTP Interface 就是它的官方对应物。定义一个接口,用 @HttpExchange 系列注解描述请求:
@HttpExchange("/api/users")
public interface UserClient {
@GetExchange("/{id}")
User get(@PathVariable long id);
@PostExchange
User create(@RequestBody User user);
}
然后让 HttpServiceProxyFactory 基于一个 RestClient 生成代理实现:

RestClient rc = RestClient.builder().baseUrl(baseUrl).build();
UserClient users = HttpServiceProxyFactory
.builderFor(RestClientAdapter.create(rc))
.build()
.createClient(UserClient.class);
注意这里的分层:@HttpExchange 只负责“声明”,真正执行请求的仍然是 RestClient(也可以换成 WebClient 适配器得到响应式版本)。所以超时、拦截器、认证这些横切配置都配在底下那个 RestClient 上,接口本身保持干净。
Spring Framework 7 进一步解决了“接口一多,样板代码就多”的问题,新增了按组注册的 @ImportHttpServices。同一组的接口共享一个 HTTP 客户端配置:
@Configuration
@ImportHttpServices(group = "stackexchange", types = {StackOverflowClient.class, ServerFaultClient.class})
@ImportHttpServices(group = "github", basePackageClasses = GitHubProjects.class)
public class ClientConfig {
}
对于原本为了声明式调用才引入 OpenFeign 的项目,这意味着在不需要 Spring Cloud 其他组件时,可以少一个依赖。但如果你重度依赖 Feign 与 Spring Cloud LoadBalancer、熔断器的集成,迁移前要逐项确认替代方案,不必为了“官方”二字强行替换。
四、WebClient 还有位置吗
有,而且位置很清晰。WebClient 属于 spring-webflux,是基于 Reactor 的非阻塞客户端。RestClient 是同步阻塞的,一个请求占住一个线程直到响应返回;WebClient 则可以在少量线程上同时挂起大量请求,并用 Mono.zip、Flux.merge 等操作符组合多个下游调用。
典型场景是“聚合接口”:一个页面要同时调三四个下游服务,用 RestClient 串行调用的耗时是各接口之和,用 WebClient 并发调用的耗时接近最慢的那一个。本文的实验里,两个各需 1 秒的慢接口并发聚合,总耗时约 1.2 秒。

另一个常被忽略的变化是依赖打包。过去 RestClient 和 RestTemplate 位于 spring-web,WebClient 位于 spring-webflux,Spring Boot 很难判断你引入这些包是想启动 Web 服务器,还是只想用 HTTP 客户端。Spring Boot 4.0 完成了模块化拆分,新增了 spring-boot-starter-restclient 和 spring-boot-starter-webclient 两个启动器,专门表达“我需要一个 HTTP 客户端”。只引 WebClient 不再意味着顺带拉起一整套 WebFlux 服务端。
五、动手实验:四种写法在 Spring Boot 4.1.1 上的真实表现
为了验证上面的结论,我写了一个不依赖任何外部服务的对照工程:用 JDK 自带的 HttpServer 起一个本地桩服务,模拟“用户中心”接口,包含正常查询、404 业务错误、写入和一个固定耗时 1 秒的慢接口;然后分别用四种客户端调用它。
依赖只有两个启动器(Spring Boot 4.1.1,对应 Spring Framework 7.0.9,JDK 21):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-restclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webclient</artifactId>
</dependency>
超时统一通过 JdkClientHttpRequestFactory 配置,它基于 JDK 11+ 的 java.net.http.HttpClient:
HttpClient http = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(2)).build();
JdkClientHttpRequestFactory f = new JdkClientHttpRequestFactory(http);
f.setReadTimeout(Duration.ofMillis(300));
运行 mvn spring-boot:run 的实际输出:
== 1. RestTemplate
GET -> User[id=7, name=用户7, email=u7@example.com, apiVersion=null]
404 -> 抛出 NotFound,响应体:{"code":"USER_NOT_FOUND","message":"用户不存在"}
== 2. RestClient
GET -> User[id=8, name=用户8, email=u8@example.com, apiVersion=1.2]
404 -> 由 onStatus 转换为 业务异常 404
== 3. @HttpExchange 接口
GET -> User[id=9, name=用户9, email=u9@example.com, apiVersion=null]
POST -> User[id=1001, name=新用户, email=new@example.com, apiVersion=null]
超时 -> 302 ms 后抛出 ResourceAccessException
== 4. WebClient
两个 1 秒的慢接口并发聚合,总耗时 1229 ms,结果数 2
几个值得注意的细节:
1. API 版本头只出现在 RestClient 那一行。桩服务把收到的 API-Version 请求头原样回显,只有配置了 defaultApiVersion("1.2") 的客户端带上了它。
2. 404 的处理风格不同。RestTemplate 抛出带响应体的 HttpClientErrorException.NotFound;RestClient 通过 onStatus 在调用点直接转成业务异常。
3. 读超时 300 毫秒,实际 302 毫秒抛出 ResourceAccessException,说明超时配置在 RestClient 和 @HttpExchange 代理上都生效了。
4. WebClient 并发两个 1 秒请求,总耗时约 1.2 秒,而不是串行的 2 秒以上。多出的 0.2 秒主要是首次建连和 Reactor 初始化开销。
整个工程只有 3 个 Java 文件(启动类、桩服务和 UserClient 接口),外加一个 pom.xml,核心片段都已在上文给出。
六、迁移排坑清单
在实验和整理资料的过程中,有几处坑值得单独列出来:
1. 预览期 API 改名。官方博客发布时示例写的是 .defaultVersion("1.2"),但 Spring Framework 7.0 正式版里这个方法叫 defaultApiVersion。我第一次编译就在这里报了 cannot find symbol。照着早期博客或文章抄代码时,以正式版 Javadoc 为准。
2. RestTemplate 默认没有超时。new RestTemplate() 默认使用 SimpleClientHttpRequestFactory,连接和读取超时默认为无限等待。下游一卡,调用线程就跟着卡死,线程池随之被打满。迁移是个好时机:给每个 RestClient 显式配置超时。
3. 不要在迁移中改变错误语义。RestClient 的 retrieve() 默认同样会在 4xx/5xx 时抛 RestClientResponseException 系列异常,但如果你在新代码里顺手加了 onStatus 吞掉错误,上层原本依赖异常的重试或降级逻辑就会悄悄失效。
4. 渐进迁移可以先“包一层”。RestClient.create(restTemplate) 可以基于现有 RestTemplate 实例创建 RestClient,复用其请求工厂、拦截器和消息转换器。先把调用点换成 RestClient API,再逐步把 RestTemplate 的配置迁走。
5. 检查依赖是否齐全。Spring Boot 4 模块化之后,HTTP 客户端的自动配置拆到了独立模块。如果注入 RestClient.Builder 时提示找不到 Bean,先确认是否引入了 spring-boot-starter-restclient。
6. Jackson 大版本。Spring Boot 4 默认使用 Jackson 3,核心包名从 com.fasterxml.jackson 变成了 tools.jackson(注解包 com.fasterxml.jackson.annotation 保持不变)。自定义过 ObjectMapper 或消息转换器的项目,升级时这部分改动往往比 HTTP 客户端本身还大。
七、选型建议
把四种写法放在一起,可以得到一张简单的决策表:
| 方案 | 编程模型 | 适合场景 | 新项目是否推荐 |
|---|---|---|---|
| RestTemplate | 同步,模板方法 | 存量代码维护 | 不推荐,7.1 起标记弃用 |
| RestClient | 同步,流式 API | 绝大多数 Spring MVC 项目的默认选择 | 推荐 |
@HttpExchange + RestClient |
同步,声明式接口 | 调用方多、接口稳定的下游服务 | 推荐 |
| WebClient | 非阻塞,响应式 | WebFlux 项目、并发聚合、流式响应 | 按需使用 |
如果项目已经在 JDK 21 上启用了虚拟线程,同步阻塞调用的线程成本会显著下降,RestClient 在并发场景下的劣势也随之缩小。这种情况下,只为“性能”把同步代码改写成响应式的必要性就更低了,WebClient 更适合那些本身就需要流式处理或背压控制的场景。
八、总结
RestTemplate 的退场是有计划、有缓冲期的:7.1 标记弃用,8.0 移除,开源支持至少到 2029 年。对开发者来说,真正需要做的是在新代码里停止使用它:同步调用默认用 RestClient,下游接口多时用 @HttpExchange 声明式接口,确实需要非阻塞并发时再引入 WebClient。
迁移本身并不难,RestClient 和 RestTemplate 共用底层设施,可以先包一层再逐步替换。更值得借这次机会补上的,是那些一直被默认值掩盖的问题:没有超时的调用、语义模糊的错误处理,以及散落各处的客户端配置。
参考资料:Spring 官方博客《The state of HTTP clients in Spring》(2025-09-30)、Spring Framework 参考文档 REST Clients 章节。
💡 在线实战体验:本文配套免安装的云端 Linux 交互式实验环境与终端操作,可在 边学边练平台 (https://www.skillup.host/) 直接体验运行验证。