推荐继承 RuntimeException 自定义业务异常类,不强制捕获、语义清晰、便于统一处理,符合 Spring 等主流框架习惯;需提供多参构造函数、封装错误码与消息模板,并配合断言工具和全局处理器落地。

推荐用继承 RuntimeException 的方式自定义业务异常类,不强制捕获、语义清晰、便于统一处理,也符合主流框架(如 Spring)的异常管理习惯。
明确异常类型:选 RuntimeException 而非 Exception
业务错误(如“用户不存在”“余额不足”)不是系统资源缺失或 I/O 故障这类必须显式处理的问题,而是规则校验失败,属于“预期失败”。用受检异常(继承 Exception)会迫使每层都 throws 或 try-catch,徒增冗余代码,且掩盖真实意图。
- 继承 RuntimeException → 非受检异常,调用方无需强制处理,异常可自然向上抛到全局处理器
- 避免继承 Exception → 除非该错误必须由调用方立刻决策重试或降级(极少数场景,如支付网关临时不可用)
- 绝对不要直接 throw new RuntimeException("xxx") → 缺乏错误码、无法结构化携带上下文、日志难排查
设计健壮的构造函数
异常链和日志诊断依赖构造函数签名。只提供一个带 message 的构造器,会导致包装底层异常时丢失原始堆栈,根因难以定位。
- 必须有:BusinessException(String message) —— 简单抛出场景
- 必须有:BusinessException(String message, Throwable cause) —— 包装 DAO 层 SQLException、Feign 调用异常等,保留原始异常栈
- 建议加:BusinessException(ErrorCode code, String message) 或 BusinessException(String code, Object... args) —— 解耦错误码与提示文案,支持动态填充(如 "订单 %s 已完成,不可取消")
封装错误码与消息模板
错误码是业务异常的“身份证”,用于前端提示、监控告警、多语言适配。不应硬编码在异常类里,也不应混入 HTTP 状态码(400/404 是协议层概念,RPC 或内部服务不适用)。
立即学习“Java免费学习笔记(深入)”;
- 定义 ErrorCode 枚举,值为纯数字(如 1001、2003)或业务标识符(如 USER_NOT_FOUND、ORDER_STATUS_INVALID)
- 消息模板建议放在枚举中或独立配置中心,避免散落在各处:USER_NOT_FOUND → "用户不存在(ID:%s)"
- 异常类中只持有一个
code字段 + 对应 getter,不负责拼接最终提示
配合断言工具与全局处理器落地
自定义异常的价值,在于被高频、轻量、可读地使用。推荐封装静态断言工具类,替代零散 if-throw。
- 写一个 Asserts 工具类,例如:
Asserts.notNull(user, ErrorCode.USER_NOT_FOUND, userId) - Spring 项目中搭配
@ControllerAdvice + @ExceptionHandler<BusinessException>,统一返回标准 JSON(含 code、message、timestamp) - 事务控制注意:Spring 默认对 RuntimeException 回滚,但像“库存不足”这类业务拒绝,实际未修改 DB,应在异常处理器中跳过回滚(或改用 NoRollbackFor)


















