exceptionally仅捕获上游异常,不处理null等业务失败值;handle则统一响应成功与异常,需手动判空,且其中抛异常会传播,而exceptionally中抛异常会被吞掉。

exceptionally 只捕获上游异常,不能处理正常返回值
当你用 exceptionally 捕获异常时,它只在上游 CompletableFuture 以 completeExceptionally 结束(或抛出未捕获异常)时触发;如果上游成功完成,哪怕返回 null 或逻辑错误值,exceptionally 完全不执行。
- 典型误用:想用
exceptionally把空结果转成默认值——这不行,它对null、false、-1 这类“业务失败”无感 - 适合场景:网络超时、NPE、IO 异常等真正中断链路的错误,需要兜底返回一个安全对象(如空集合、默认配置)
- 参数只有一个:
Throwable,无法访问原始计算结果,所以没法做“异常时查缓存”这类依赖上下文的恢复
future.exceptionally(t -> {
log.error("call failed", t);
return Collections.emptyList(); // 只能靠 Throwable 做判断
});
handle 能同时响应成功和异常,但必须自己区分路径
handle 是真正的二合一回调:无论上游是 complete 还是 completeExceptionally,它都会执行,且把结果(T)和异常(Throwable)都传进来。但它不自动分流,你得手动判空。
- 常见错误:忽略
throwable == null判断,直接对result做非空操作,导致 NPE - 性能影响:即使上游成功,
handle也必然执行,比单纯thenApply多一次函数调用开销,高并发下可感知 - 兼容性好:Java 8+ 全支持,不像
whenCompleteAsync那样需注意线程池选择
future.handle((result, throwable) -> {
if (throwable != null) {
return fallbackByError(throwable); // 自定义降级逻辑
}
return validateAndWrap(result); // 正常路径处理
});
不要用 exceptionally 替代业务校验,否则会掩盖逻辑缺陷
很多同学把字段校验失败、HTTP 400 响应、数据库查不到记录这些本该走正常流程的 case,硬塞进异常流,再用 exceptionally 拦截——这会让错误语义混乱,调试时分不清是系统崩了还是业务没数据。
- HTTP 调用返回 404:应该解析响应体,走正常
thenApply分支处理,而不是抛RuntimeException再进exceptionally - 数据库查询为空:用
Optional<User>作为返回类型,比抛NoResultException更符合语义 - 真正该进
exceptionally的只有:连接 refused、JSON 解析失败、线程池满等不可控底层问题
handle 中 throw 异常会传播,exceptionally 中 throw 会吞掉异常
这是最易踩的坑:handle 方法体里如果抛出新异常,它会继续向下游传播(变成下一个 exceptionally 或 handle 的 throwable 参数);而 exceptionally 里抛异常,CompletableFuture 会静默吞掉,下游收不到任何信号——看起来像“链路断了”,实际是异常被吃掉了。
立即学习“Java免费学习笔记(深入)”;
- 排查技巧:如果发现某个
exceptionally后面的thenAccept不执行,先检查这个exceptionally方法体里有没有意外抛异常 - 安全写法:在
exceptionally里加try-catch,或确保返回值构造绝对安全 -
handle更可控:你可以主动 throw,也可以返回特殊标记值,下游按需处理
exceptionally 和 handle 才不会互相打架。










