应根据责任归属与恢复能力选择:可感知、可响应、需主动处理的外部不确定性用受检异常;自身逻辑缺陷或非法状态用非受检异常。

自定义异常时,选受检还是非受检,核心不是看“要不要写try-catch”,而是看“谁该为这个问题负责、有没有能力恢复”。
该用受检异常的场景
当异常代表一种调用方**能感知、可响应、应主动处理**的外部不确定性时,就该继承 Exception(非 RuntimeException 子类)。
- 支付接口返回“余额不足”或“风控拒绝”,下游服务需要重试、降级或提示用户——这不是代码写错了,是业务流程中的正常分支,必须强制调用方考虑应对策略
- 调用第三方证书服务验证身份,可能因网络抖动或证书过期失败,上层需切换备用通道或引导用户刷新——这类外部依赖故障可恢复,且责任在调用逻辑而非实现细节
- 文件导入功能中,“模板格式不匹配”虽由用户上传引起,但系统可识别并返回结构化错误码+修复建议,属于可控的业务校验失败,适合定义为受检异常(如
InvalidTemplateException extends Exception)
该用非受检异常的场景
当异常反映的是**当前方法自身逻辑缺陷、参数非法或状态不一致**,且本不该发生——那就继承 RuntimeException,让问题早暴露、早修正。
- 订单服务中传入 null 订单ID,直接抛
IllegalArgumentException或自定义MissingOrderIdException extends IllegalArgumentException,而不是捕获后吞掉 - 状态机流转时,从“已发货”强行调用“取消订单”,违反业务规则,应抛
IllegalStateException子类,而非设计成受检异常迫使每个调用都写兜底 - 内部工具类接收一个非空集合,却收到 null,立刻用
Objects.requireNonNull(list)触发NullPointerException——这是开发阶段就该拦截的错误,不归运行时兜底
容易踩坑的混淆点
别把“想省事”当成技术决策依据。
- 把本该受检的业务异常(如库存扣减失败)改成 RuntimeException,等于把可重试的资源竞争问题,伪装成不可救的程序 bug,上游无法区分是系统崩了还是库存卖光了
- 在 service 层对 DAO 抛出的 SQLException 做无意义的 try-catch + throw new RuntimeException(e),既丢失原始异常类型,又让调用方失去处理机会;更合理的是包装为受检的
InventoryUpdateException extends Exception,语义清晰且可追溯 - 认为“所有校验失败都要用非受检异常”,忽略了校验来源:参数来自 API 请求体?用非受检;校验来自外部配置文件加载结果?那可能是 IOException 的衍生问题,更适合受检
一句话判断法
问自己:如果这个异常发生了,当前方法的直接调用方,是否具备信息和手段去做出有意义的响应(比如换渠道、告警、提示用户、重试)?
- 能——定义为受检异常,推给调用方显式决策
- 不能,或者本就不该发生——定义为非受检异常,靠编码规范和测试提前卡住

















