异步异常处理需区分系统级中断与业务级信号:exceptionally仅响应执行失败(如超时、NPE),不处理业务约定错误;handle是统一入口,可同时访问结果与异常,适合日志、校验、降级;值状态错误应封装为Optional等走正常链路,而非抛异常。

异步异常传递的关键,在于分清“系统级中断”和“业务级信号”。handle 和 exceptionally 不是互换的备选方案,而是职责分明的两个环节:一个负责统一收口、上下文联动;另一个专注底层兜底、快速熔断。
exceptionally 只响应真正的执行失败
它只在 CompletableFuture 被 completeExceptionally() 显式标记异常,或上游 lambda 抛出未捕获运行时异常时触发。返回 null、空集合、HTTP 404 响应体、数据库查无结果——这些都不算,它完全不感知。
- 适合拦截:连接超时、JSON 解析失败、NPE 发生在 IO 回调中、线程池拒绝等不可控底层问题
- 不适合拦截:字段校验不通过、状态码非 2xx、返回值为 null 的业务约定、-1 表示失败的整型协议
- 注意:内部抛出的新异常会被静默吞掉,不会继续传播;也不能访问原始计算结果,只能靠 Throwable 类型做简单分类
handle 是唯一能同时看到结果和异常的统一入口
无论上游成功还是失败,handle 都会执行,参数固定为 (T result, Throwable throwable)。result 在异常时为 null,throwable 在成功时为 null——这是它的契约,不是 bug。
- 必须先判空再使用:if (throwable != null) { … } else if (result != null) { … }
- 可做统一日志、字段校验、空值转 Optional、缓存回源、状态码分支降级(如 404 返回默认用户)
- 内部若抛异常,会原样向后传播,后续 thenApply 等阶段可能因未处理而中断
变量流错误要分层处理,不能全塞进异常链
所谓“变量流错误”,比如 HTTP 响应解析后发现 user.id == null、列表 size() == 0、金额为负数——这些本质是数据状态判断,不是执行崩溃。强行 throw RuntimeException 再进 exceptionally,会模糊错误语义,让监控误判为系统故障。
- 推荐做法:把这类值用 Optional、Result<T>、Response<T> 封装,走正常 thenApply 分支处理
- 如果必须兼容老接口返回 null,就在 handle 里集中判空并 fallback,而不是制造异常
- 真正需要 exceptionaly 的,只是那些连 Response 对象都构造不出来的场景:IO 中断、反序列化失败、线程被 kill
组合使用才能覆盖完整链路
一个健壮的异步链,往往需要多层防御:
- IO 调用后立刻接 exceptionally:屏蔽网络层不稳定,返回安全默认值(如 emptyList)
- 数据转换(thenApply)后加 handle:校验字段、包装结构、对业务错误码做响应式降级
- 整个链末尾补一层 exceptionally:兜底捕获 handle 里自己抛出的异常,避免静默失败

















