CompletableFuture异常处理分“捕获感知”和“恢复兜底”:handle统一收口做可观测性与分支判断,exceptionally专做同类型降级返回,whenComplete专注副作用清理;三者常链式组合使用。

CompletableFuture 处理异步任务异常,核心是区分“捕获感知”和“恢复兜底”两个层次:用 handle 统一收口做可观测性与分支判断,用 exceptionally 专做类型一致的降级返回,二者常串联使用。
exceptionally:只在出错时执行,返回同类型默认值
它像一道安全闸门,仅当前链中任意上游阶段抛出未捕获异常(包括 RuntimeException、NPE、流操作异常等)时触发,正常完成则完全跳过。
- 参数是 Throwable,必须返回与原始 CompletableFuture 泛型类型相同的值
- 适合提供缓存数据、空集合、占位字符串等轻量级 fallback
- 无法拿到原始成功结果,也不方便按异常类型做多路分支(需配合 instanceof)
示例:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (Math.random() > 0.5) throw new IOException("连接超时");
return "data";
}).exceptionally(ex -> {
log.warn("降级响应", ex);
return "fallback"; // 类型匹配 String
});
handle:无论成败都执行,支持结果与异常双路径处理
它接收两个参数:result(成功时非 null,失败时为 null)和 ex(失败时非 null,成功时为 null),天然支持“二选一”逻辑分支。
- 可统一加日志、打监控指标、转换结果格式,或根据异常类型返回不同降级值
- 返回类型可变(泛型自由),比如从 String 转成 Result<String>
- 若在 handle 中主动 throw 新异常,该异常会继续向下游传播
示例:
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 100)
.thenApply(x -> x / 0) // 故意触发 ArithmeticException
.handle((result, ex) -> {
if (ex != null) {
if (ex instanceof ArithmeticException) return -1;
else throw new RuntimeException("不可恢复异常", ex);
}
return result * 2;
});
whenComplete:不改变结果,专注副作用清理
类似 finally,一定执行,但不参与结果计算。适合资源释放、最终状态上报、耗时统计等不影响业务值的操作。
立即学习“Java免费学习笔记(深入)”;
- 参数也是 (result, ex),但方法签名返回 void
- 不能修改结果或返回新值,仅用于“事后记录”
- 常与 exceptionally 或 handle 配合:前者管恢复,后者管收尾
示例:
future.whenComplete((result, ex) -> {
if (ex != null) {
metrics.counter("api.fail").increment();
} else {
metrics.timer("api.latency").record(System.nanoTime() - start);
}
});
组合策略:可观测性 + 强兜底
生产环境推荐链式写法:先 handle 做分类记录与轻量转换,再 exceptionally 做最终保底,确保调用方总能拿到可用值。
- handle 插入监控逻辑,不中断链;exceptionally 放在末尾,兜住所有漏网异常
- 避免在 handle 中静默吞异常却不处理——要么记录,要么重抛,要么转成业务异常
- 注意 CompletionException 是包装器,真正原因在 ex.getCause(),日志建议打印 root cause


















