completablefuture.exceptionally仅在直接前置stage抛出未捕获异常时触发,用于单个异步任务失败降级;不处理取消、超时或上游已吞异常,参数为throwable且须返回同类型值。

CompletableFuture.exceptionally 什么时候会触发
exceptionally 只在 CompletableFuture 的异步计算抛出未捕获的异常时触发,且仅对当前 stage 生效。它不是“全局异常兜底”,也不会捕获 cancel(true)、TimeoutException(除非显式 throw)、或上游已用 handle/whenComplete 吞掉的异常。
- 常见错误现象:
exceptionally完全没执行,但控制台打印了java.util.concurrent.CompletableFuture$AsyncSupply@... completed exceptionally—— 很可能上游用了thenApply并在其中throw new RuntimeException,但没链上exceptionally - 使用场景:适合做单个异步任务的“失败降级”,比如调用第三方 API 失败时返回缓存值或默认对象
- 参数差异:接收一个
Throwable参数,必须返回与原始计算结果同类型的值(不能 void,也不能改类型)
为什么 exceptionally 不处理 InterruptedException
因为 InterruptedException 默认被 CompletableFuture 内部吞掉并转为取消状态(isCancelled() == true),不视为“异常完成”。除非你手动在任务中 throw new RuntimeException(e) 包装它。
- 容易踩的坑:用
supplyAsync(() -> { Thread.sleep(1000); return "ok"; })+exceptionally,再调future.cancel(true)——exceptionally不会进,得靠whenComplete((r, t) -> { if (t instanceof CancellationException) {...} }) - 性能影响:强行包装
InterruptedException会增加栈深度和对象分配,非必要不建议 - 兼容性注意:Java 19+ 对中断传播更严格,但
exceptionally行为没变
exceptionally 和 handle/whenComplete 的关键区别
exceptionally 是纯“异常分支”,只在失败时走;而 handle 和 whenComplete 是“无论成败都执行”,但语义不同:前者可修改结果(返回新值),后者只做副作用(如打日志、发通知)且不能改结果。
- 常见错误现象:想用
exceptionally打日志 + 返回默认值,却写成exceptionally(e -> { log.error("", e); return "fallback"; })—— 没问题;但如果写成exceptionally(e -> { log.error(""); return null; }),而原始类型是String,null 是合法的,但后续join()可能 NPE,得自己判空 - 使用场景:需要统一 fallback 值就用
exceptionally;要区分成功/失败做不同清理动作,用whenComplete;要根据异常类型返回不同 fallback,用handle - 参数差异:
exceptionally参数是Throwable;handle是(T, Throwable);whenComplete是(T, Throwable)但返回 void
链式调用中 exceptionally 的作用域边界
exceptionally 只捕获它**直接前置 stage** 抛出的异常,不跨多个 thenXXX 向上传播。比如 a.thenApply(...).thenApply(...).exceptionally(...),只管第二个 thenApply 的异常,第一个的异常会直接中断链条。
立即学习“Java免费学习笔记(深入)”;
- 容易踩的坑:以为加在最后就能兜住整条链,结果第一个
thenApply抛异常,整个 future 状态变成EXCEPTIONAL,但exceptionally不触发 —— 因为它绑定的是前一个 stage,不是整条链 - 实操建议:每个可能出错的
thenApply/thenCompose后面单独接exceptionally,或者统一用handle替代 - 性能考虑:频繁加
exceptionally会略微增加对象创建(每个都 new 一个 Function),高并发场景下可复用 lambda 实例
真正难处理的是嵌套异步(比如 thenCompose 里又 supplyAsync),这时候异常发生在子 future 里,父 future 看不到,得靠子 future 自己配 exceptionally。










