Java 项目里调用外部接口,看起来只是“发个 HTTP 请求”,但真到项目里经常会纠结:用 JDK 自带的 HttpClient,还是 Spring 的 RestTemplate、RestClient、WebClient?临时脚本能不能直接用 Hutool?需要连接池、超时、重试、日志时又该怎么收口?
我的经验是,不要先问哪个库“最好”,而要先问这个请求会不会长期存在、是否在 Spring 项目里、调用量大不大、是否需要响应式链路。
先给结论
可以先按这个顺序选:
| 场景 | 推荐选择 |
| — | — |
| Spring Boot 3 同步业务接口 | RestClient |
| 老项目已经大量使用 RestTemplate | 继续维护 RestTemplate,新代码逐步迁到 RestClient |
| 响应式 WebFlux 项目 | WebClient |
| 非 Spring 项目、只想少依赖 | JDK java.net.http.HttpClient |
| Android、通用 Java 客户端、需要成熟连接能力 | OkHttp |
| 本地小工具、临时脚本、一次性批处理 | Hutool HttpUtil |
如果是正式后端服务,我会优先选 RestClient 或 WebClient;如果只是写一个个人工具,Hutool 或 JDK HttpClient 往往更省心。
RestTemplate:老项目里还会长期存在
RestTemplate 是很多 Spring 老项目里最常见的同步 HTTP 客户端。它的优点是资料多、团队熟悉、写法直接:
String body = restTemplate.getForObject(url, String.class);
但它不太适合作为新项目的首选。Spring 现在更推荐同步场景使用 RestClient,响应式场景使用 WebClient。所以对老项目来说,比较稳妥的策略不是立刻全量替换,而是:
- 已经稳定运行的
RestTemplate调用先不要大动。 - 新增接口调用优先使用
RestClient。 - 统一补齐连接超时、读取超时、错误处理和日志。
- 后面碰到相关模块改造时,再顺手迁移。
这样比为了“换新工具”一次性改大量外部接口更安全。
RestClient:同步 Spring 项目的新默认
RestClient 可以理解成 Spring 里更现代的同步 HTTP 客户端。它的使用体验接近 WebClient 的链式写法,但执行模型仍然是同步的,适合大多数 Spring MVC 后端服务。
示例:
String result = restClient.get()
.uri("https://example.com/api/user/{id}", userId)
.retrieve()
.body(String.class);
它适合这些场景:
- Spring Boot 3 项目。
- 普通同步接口调用。
- 希望代码比
RestTemplate更清晰。 - 不想为了一个普通接口引入响应式编程心智。
如果团队没有 WebFlux 背景,也没有响应式链路需求,我更倾向先用 RestClient。
WebClient:别只因为“新”就用
WebClient 是响应式客户端,适合和 WebFlux、Reactor、非阻塞调用链一起使用。它的能力很强,但也意味着项目里会出现 Mono、Flux、响应式错误处理、调度器等概念。
如果只是普通 Spring MVC 项目,接口调用量也不大,直接上 WebClient 不一定更简单。常见问题是:调用处最后还是 .block(),那响应式优势没吃到,复杂度却留下了。
我一般这样判断:
- 项目本身就是 WebFlux:用
WebClient。 - 需要大量并发 I/O 编排,并且团队能维护响应式链路:用
WebClient。 - 只是后台系统调用几个三方接口:优先
RestClient。
工具不是越高级越好,能让维护成本变低才算选对。
JDK HttpClient:少依赖时很香
JDK 11 之后自带 java.net.http.HttpClient,可以发同步和异步请求,也支持 HTTP/2。对于非 Spring 项目、命令行工具或比较轻的模块,它是一个很好的默认选项。
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com/api"))
.timeout(Duration.ofSeconds(10))
.GET()
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
它的好处是不用额外依赖,部署和升级都简单。缺点是和 Spring 的拦截器、消息转换、统一配置体系没有那么贴合。正式 Spring 业务服务里,我还是会优先使用 Spring 体系内的客户端。
OkHttp:成熟客户端,但别无脑引
OkHttp 在 Java 和 Android 生态里都很成熟,连接池、拦截器、超时配置、请求日志这些能力都很完整。如果项目本身已经用 OkHttp,继续用是合理的。
但在 Spring Boot 后端里,如果只是普通 REST 调用,我不会为了一个简单接口额外引 OkHttp。除非你明确需要它的某些能力,或者已有内部封装已经围绕 OkHttp 建好。
选型里最怕的是每个模块各用一套客户端:A 用 RestTemplate,B 用 OkHttp,C 用 Hutool,D 又自己手写。最后排查超时、代理、日志、重试时会很痛苦。
Hutool HttpUtil:适合工具,不适合铺满业务核心
Hutool 的 HttpUtil 很适合写小工具。比如临时调一个接口、批量下载一点数据、做一个本地脚本,它能少写很多样板代码。
但我不太建议把它铺到核心业务里。原因不是 Hutool 不好,而是正式业务通常需要更明确的工程边界:
- 超时和连接池配置要统一。
- 错误码、异常、重试要可控。
- 日志脱敏和审计要统一。
- 代理、证书、网络异常要能排查。
- 单元测试里要方便替换或 Mock。
所以它更像瑞士军刀:随身好用,但不要拿它当整套工程的地基。
UserAgent 解析要异步和缓存
源 note 里还记录了 UserAgent 解析工具。这个问题也很典型:看起来只是解析一个字符串,但如果库比较重、规则初始化慢、每次解析耗时高,就不应该放在主请求链路里硬跑。
更稳的做法是:
- 解析器对象尽量复用,不要每次请求都重新初始化。
- 低价值字段异步补全,别拖慢主接口。
- 高频相同 UserAgent 可以加本地缓存。
- 解析失败不要影响主流程。
这类细节和 HTTP 客户端选型一样,本质上都是把“临时代码”变成“可长期运行的工程代码”。
正式项目要统一封装一层
无论最后选哪个客户端,正式项目里都建议封装一层自己的 ExternalApiClient 或按业务命名的客户端类,不要在各个 Service 里散落 HTTP 细节。
这一层至少负责:
- 统一 baseUrl、超时、请求头和鉴权。
- 统一异常转换,不把底层 HTTP 异常直接丢给业务层。
- 统一日志和敏感字段脱敏。
- 统一响应解析和错误码判断。
- 为测试留出替换点。
这样以后从 RestTemplate 换到 RestClient,或者从云端接口换到本地服务,影响面会小很多。
小结
Java HTTP 客户端选型可以简单记成一句话:
Spring 同步项目优先 RestClient,响应式项目用 WebClient,老项目维护 RestTemplate,非 Spring 小工具看 JDK HttpClient 或 Hutool,已有成熟封装时再考虑 OkHttp。
真正重要的不是把所有客户端都背一遍,而是把超时、错误处理、日志、重试和测试边界统一起来。HTTP 调用越像基础设施,越不能散落在业务代码里随手写。




