java 11 httpclient 异步请求需用 sendasync() 配合 completablefuture 回调处理,设全局 timeout() 控制生命周期,复用单例客户端,响应流需手动关闭,错误需开启日志调试。

Java 11 HttpClient 异步请求怎么发(不阻塞主线程)
直接用 HttpClient.newBuilder().build() 配合 sendAsync(),不是 send() —— 后者是同步的,哪怕你丢进线程池也改不了它内部阻塞 I/O 的本质。
常见错误是写了 sendAsync() 却没处理 CompletableFuture 的回调,结果请求发出去了,但响应没人接,或者异常被静默吞掉。
-
sendAsync()返回CompletableFuture<httpresponse>></httpresponse>,必须显式调用thenApply()、exceptionally()或join()(仅调试用) - 别在 Servlet 容器或 Spring WebMvc 线程里直接
join(),会卡住工作线程 - 如果要等多个异步请求,用
CompletableFuture.allOf(),但注意它不聚合响应体,得自己 map 收集
RequestBody 和 BodyHandler 怎么选才不出错
异步请求里最容易栽在类型不匹配:比如用 BodyHandlers.ofString() 却收到二进制响应,抛 java.nio.charset.MalformedInputException;或者传 HttpRequest.BodyPublishers.ofString() 给一个期待 JSON 的 API,但没设 Content-Type: application/json。
- 发送 JSON 时,手动加头:
header("Content-Type", "application/json"),HttpClient不自动推断 - 接收大文件别用
ofString()或ofByteArray(),改用ofFile(Paths.get(...))或自定义BodyHandler流式处理 - 如果响应体可能为空(如 204),
ofString()会返回空字符串,没问题;但ofInputStream()会给你一个已关闭的流,读就抛IOException
连接超时和响应超时必须分开设
Java 11 的 HttpClient 把超时拆成两段:连接建立(connect timeout)和响应读取(read timeout),但默认都为 0(无限等待)。线上环境不设就是埋雷。
立即学习“Java免费学习笔记(深入)”;
- 用
HttpRequest.Builder.timeout(Duration.ofSeconds(5))控制整个请求生命周期(含连接 + 响应读取),简单粗暴 - 真要分控,得靠
HttpClient.Builder.connectTimeout(Duration)(JDK 14+ 才支持,Java 11 不行)→ 所以 Java 11 只能靠上面那个全局timeout() - 注意:这个
timeout()对sendAsync()也生效,超时后CompletableFuture会以InterruptedException或TimeoutException完成
为什么用完 HttpClient 不关也没事,但别滥用
HttpClient 内部用连接池和后台线程调度,实例本身是线程安全且可复用的。官方建议复用单例,而不是每次 new 一个。
- 如果你用
HttpClient.newBuilder().build()每次新建,连接池无法复用,短连接暴涨,容易触发java.net.SocketException: Too many open files - 不用显式 close
HttpClient,除非你调了executor()自定义了线程池——那时得自己管理线程池生命周期 - 真正要关的是
HttpResponse.body()返回的流(如InputStream),尤其用ofInputStream()时,不关会泄漏 socket
HttpClient 异步能力够用,但它的错误提示很沉默,比如 DNS 失败、SSL 握手失败、重定向循环,往往只抛泛型 IOException,得打开 jdk.internal.httpclient.HttpClientImpl 日志(加 JVM 参数 -Djdk.httpclient.HttpClientImpl.level=ALL)才能看清底层发生了什么。










