应使用继承 RuntimeException 的自定义业务异常类,重载全部四个构造方法并显式传递 cause;getMessage() 仅返回业务语义消息;全局处理器中保留原始异常链;禁止用通用 RuntimeException 替代明确业务异常类型。

避免 RuntimeException 覆盖业务异常信息,核心是不让原始业务异常被“吞掉”或“替换”,尤其要防止在包装、转换、日志记录或全局处理过程中丢失 cause、错误码、上下文等关键字段。
确保所有构造方法都正确传递 cause
自定义业务异常(如 BusinessException)必须继承 RuntimeException,并重载全部四个标准构造方法:无参、仅 message、仅 cause、message + cause。每个构造方法内部必须调用 super(…) 且显式传入 cause(哪怕为 null),否则原始异常链会断裂。
- ✅ 正确写法:
super(message, cause)或super(cause) - ❌ 错误写法:
super(message); initCause(cause)—— initCause 有调用限制,且可能被忽略或覆盖 - ❌ 危险写法:
new RuntimeException("包装后消息")—— 完全丢弃原始异常
不在 getMessage() 或 toString() 中拼接 cause 信息
业务异常的 getMessage() 应只返回业务语义消息(如“库存不足”),不要把原始异常的堆栈或 cause 描述硬塞进去。否则会导致日志中出现重复、混乱甚至掩盖真实根因的文本。
- 原始异常信息应通过
getCause()获取,这是 JDK 标准方式,工具链(如 Sentry、Logback 异常解析器)依赖它 - 若需增强提示,可单独提供
getFullMessage()方法,但不要干扰父类行为
全局异常处理器中保留原始异常链
在 @ControllerAdvice 或 WebMvcConfigurer 的异常处理逻辑里,不要用 new RuntimeException(e.getMessage()) 这类方式“重新抛出”,而应直接返回原始异常实例或其子类。
- 检查是否调用了
ex.getCause(),确认非 null 且类型符合预期 - 响应体中可同时输出
errorCode、message和rootCause(通过 ExceptionUtils.getRootCause(ex) 获取) - 避免在 catch 块中仅记录日志却不 rethrow 或包装——这等于静默吞掉异常
禁止用 RuntimeException 代替业务异常类型
业务规则失败(如“用户不存在”“余额不足”)不应抛出通用 RuntimeException,而应使用明确命名的子类(如 UserNotFoundException、InsufficientBalanceException)。
- 不同业务异常类型便于分类监控、告警和前端差异化处理
- 混用 RuntimeException 会让日志搜索、链路追踪失去语义,无法区分是代码 bug 还是正常业务拒绝
- Spring 的 @ExceptionHandler 可按类型精准捕获,避免一个 handler 处理所有 RuntimeException 导致逻辑耦合

















