应优先选用非受检异常,仅在调用方具备明确恢复能力时才使用受检异常;技术异常需在DAO/Service边界封装为语义清晰的RuntimeException子类;业务异常统一继承RuntimeException并携带错误码。

选受检还是非受检,关键不在“该不该抛”,而在于“谁该负责处理”以及“能不能合理恢复”。现代业务系统里,多数异常最终都由统一异常处理器兜底,所以重点是让异常语义清晰、边界明确、可观测性强。
看调用方有没有恢复能力
如果异常发生后,上层能主动重试、降级、切换备用路径或提示用户修正输入,那就适合用受检异常——它强制提醒调用方必须面对这个可能性。
- 比如读取远程配置失败,调用方可选择加载本地缓存或返回默认值
- 文件上传时 IO 异常,前端可提示“网络不稳定,请重试”
- 数据库连接超时,服务可启用熔断并返回友好提示
看是不是程序逻辑问题
参数为空、状态非法、类型转换错误这类问题,本质是代码缺陷,不是运行时偶然状况。这类必须用非受检异常,靠测试和校验提前暴露,而不是指望线上靠 try-catch 补救。
- 传入 null ID 查询用户 → IllegalArgumentException
- 订单已发货却尝试取消 → IllegalStateException
- JSON 解析失败但字段必填 → 自定义 InvalidRequestException
在 DAO/Service 边界做一次封装
底层技术异常(如 SQLException、IOException)本身是受检的,但不应直接穿透到业务层。要在数据访问或远程调用边界,用语义明确的 RuntimeException 子类包装,并保留原始 cause。
- DAO 层捕获 SQLException → 转为 DataAccessException 或自定义 DbException
- HTTP 客户端抛 IOException → 包装成 RemoteServiceException
- 构造时务必传入原始异常:new BusinessException("下单失败", e)
统一用错误码+非受检异常支撑业务场景
库存不足、余额不够、权限拒绝等业务规则不满足的情况,既不是编程错误,也不适合让每个调用方自己 catch 处理。推荐定义继承 RuntimeException 的业务异常基类,内置 error code、message 和 traceId。
- 避免 throw new RuntimeException("余额不足") 这种无结构写法
- 统一返回 { "code": "BALANCE_INSUFFICIENT", "msg": "账户余额不足" }
- 配合 @ControllerAdvice 全局拦截,自动记录日志、上报监控、转换响应体

















