推荐继承 RuntimeException,提供无参、含 message、含 message 和 cause 的三个构造器,添加 private final 业务字段并提供 getter,配合全局异常处理器统一响应。

直接写一个能用、好查、不踩坑的自定义异常类,关键不是堆代码,而是想清楚三件事:它代表什么业务问题、谁来处理它、排查时需要哪些信息。下面按实战中最常遇到的场景展开。
明确继承 RuntimeException 还是 Exception
大多数业务异常推荐继承 RuntimeException。比如参数校验失败、用户状态不合法、订单不存在——这些错误程序能预判,但不需要强制调用方一层层 try-catch,否则主流程会被大量异常处理代码淹没。
只有极少数强契约场景才继承 Exception,比如「实名认证未通过不得支付」,这种必须由上层显式响应,编译器会强制你加 throws 或 try-catch,避免逻辑遗漏。
- 继承 RuntimeException → 非受检异常 → 不强制捕获,适合常规业务校验
- 继承 Exception → 受检异常 → 编译报错提醒,适合必须拦截并决策的环节
- 别混用:同一个异常类不能既想强制处理又不声明 throws,编译通不过
三个构造方法一个都不能少
只写一个 BusinessException(String message) 是常见误区。真实调用中会出现三种典型情况,每种都需要对应构造器支持,否则异常链断裂或日志信息丢失。
立即学习“Java免费学习笔记(深入)”;
-
无参构造器:用于简单抛出,如
throw new UserLockedException();,仍能正常打印堆栈 -
带 message 的构造器:最常用,传入可读提示,如
new UserLockedException("用户已被管理员锁定") -
带 message 和 cause 的构造器:这是异常链的关键,必须调用
super(message, cause),确保底层数据库超时、HTTP 调用失败等原始异常完整保留
加业务字段但不干扰异常链
错误码、订单 ID、用户 ID 这些字段很有用,但加的时候要守住一条线:不能影响 cause 的传递逻辑。
- 字段声明为
private final,保证异常抛出后不可变 - 所有含 cause 参数的构造器,第一行必须是
super(message, cause),业务字段单独赋值 - 提供 getter 方法(如
getErrorCode()),不提供 setter - 避免在构造器里做耗时操作或外部调用,异常对象应轻量、确定
配合全局异常处理器统一响应
自定义异常类本身只是“信号弹”,真正起作用的是和它配套的全局处理机制。Spring Boot 中通常用 @ControllerAdvice + @ExceptionHandler 拦截,把不同异常转成标准 JSON 响应。
- 对 BusinessException 类型,返回 HTTP 200 + 错误码 + 提示,前端直接展示
- 对 SQLException 等系统异常,记录详细日志 + 返回通用错误,避免暴露敏感信息
- 不要在 service 层 catch 后又 throw 新异常却不传 cause,这是线上排查的最大黑洞


















