handle方法能同时接收result和throwable,因它是completablefuture中专为双路径设计的回调:无论上游成功complete()或失败completeexceptionally()均执行,需手动判空,否则易触发nullpointerexception。

handle 方法为什么能同时拿到 result 和 throwable
handle 是 CompletableFuture 中少数明确设计为“双路径入口”的回调方法:它不管上游是 complete() 还是 completeExceptionally(),都会执行,并把两个参数——result(成功时非 null)和 throwable(异常时非 null)——一起传进来。这和 thenApply(只收 result)、exceptionally(只收 throwable)有本质区别。
它不自动分流,也不做任何假设,所以你必须自己判断哪个非 null。漏掉判空会直接导致 NullPointerException,尤其在高并发下容易复现。
- 如果
throwable != null,说明上游出错了,result一定是null(哪怕上游返回了null,这个null也不会传进来) - 如果
throwable == null,说明上游成功完成,result就是实际返回值,可能为null(这是业务逻辑决定的,handle不干涉) - 不能在
throwable == null分支里直接调用result.toString()之类的方法,必须先判空
handle 中抛异常会中断链路,和 exceptionally 不同
exceptionally 内部抛出的异常会被吞掉,最终仍返回你指定的降级值;但 handle 里只要抛出任何未捕获异常(包括 RuntimeException),就会被封装成 CompletionException 向后传播,后续的 thenApply、thenAccept 全部跳过,除非再接一个 exceptionally 或另一个 handle。
- 想安全降级?在
throwable != null分支里 return 默认值,别 throw - 想透传特定异常?比如只对
TimeoutException降级,其他异常原样抛出,可以写throw new CompletionException(throwable) - 不要在
handle里做耗时 IO 操作(如远程日志上报),它默认在前一阶段线程中执行,可能阻塞整个链路
典型场景:HTTP 调用后统一解析响应与错误
真实业务里,异步调用 HTTP 接口后,你要处理三种情况:成功响应(200 + JSON)、客户端错误(4xx)、服务端错误(5xx)。这些都不该进 exceptionally,因为它们是协议层正常语义,不是网络中断或 JSON 解析失败这类底层异常。
正确做法是让 HTTP 客户端(如 OkHttp、WebClient)把 4xx/5xx 也当作“成功响应”返回,然后在 handle 里根据状态码分支处理:
httpFuture.handle((response, ex) -> {
if (ex != null) {
// 真正的异常:连接超时、SSL 握手失败、IO 中断
log.warn("HTTP call failed", ex);
return fallbackData();
}
if (response.statusCode() >= 400) {
// 业务可预期的错误响应,走降级或重试逻辑
return handleHttpError(response);
}
// 正常 200 响应,解析 JSON 并校验字段
return parseAndValidate(response.body());
});
- 这样所有逻辑都在一个入口,避免把 404 当异常抛、再用
exceptionally拦截,语义混乱 - 注意:如果
parseAndValidate内部抛异常,那才是该进exceptionally的地方 - 若需异步执行降级逻辑(比如查缓存),要用
handleAsync并指定线程池,否则仍在 IO 线程上跑
和 exceptionally 混用时的执行顺序容易被忽略
如果你写了 .handle(...).exceptionally(...),第二个 exceptionally 只捕获 handle 里抛出的异常,不是原始任务的异常。原始任务异常只会触发 handle 的 throwable 参数。
- 常见误写:
supplyAsync(...).handle(...).exceptionally(...)—— 这里的exceptionally实际上没机会执行,因为handle已经把所有异常路径都覆盖了 - 真正需要混用的场景是:
supplyAsync(...).handle(...).thenApply(...).exceptionally(...),即在handle处理完之后,后续某步又出了新异常 - 多数情况下,单用
handle就够了;加exceptionally属于防御性冗余,除非你明确知道后续阶段可能抛异常且不想在handle里处理










