Java自定义异常需继承RuntimeException,用枚举定义业务错误码并与HTTP状态码解耦,提供三类构造函数保障异常链完整,并通过context字段存储上下文信息。

企业级开发中,Java 自定义异常不是“多写一个类”那么简单,核心是用它把业务错误变成可识别、可分类、可追踪、可响应的结构化信号。
必须继承 RuntimeException,而非 Exception
业务异常本质是流程中的合法分支,不是程序崩溃。继承 RuntimeException 才符合语义:
- 不强制调用方写 try-catch 或声明 throws,避免污染接口契约
- @Transactional 默认对 RuntimeException 回滚,便于控制事务边界
- @RestControllerAdvice 默认只拦截非受检异常,统一处理才生效
- 极少数强契约场景(如银行 SDK 集成)才考虑受检异常,日常业务一律不用
错误码必须用枚举,且与 HTTP 状态码解耦
别在 message 里拼字符串错误码,也别直接用 400/500 当业务码。定义纯业务语义的枚举:
- ORDER_NOT_FOUND(1001)、INSUFFICIENT_BALANCE(1002)、COUPON_EXPIRED(1003)
- 好处:RPC 调用可透传;前端按 code 映射提示文案;国际化只需换 message;监控系统能按码统计错误率
- HTTP 状态码由 Controller 层统一映射(如 ORDER_NOT_FOUND → 404),和异常类本身无关
提供三类构造函数,保障异常链完整
只写一个带 message 的构造器,等于主动丢掉调试线索。必须覆盖:
立即学习“Java免费学习笔记(深入)”;
- 无参构造:
new BusinessException(),用于简单抛出,堆栈仍完整 - 带 message 构造:
new BusinessException("库存不足"),明确业务提示 - 带 message 和 cause 构造:
new BusinessException("支付失败", e),保留原始异常堆栈链
每个构造函数只要含 cause 参数,就必须调用 super(message, cause),否则 getMessage() 可能为空,日志第一行就失效。
用 context 字段存上下文,别塞进 message
用户 ID、订单号、请求参数等现场信息,应放进 Map<String, Object> context 字段,而不是拼进 message:
- message 是给人看的,context 是给系统用的——日志解析、链路追踪、告警规则都依赖它
- 字段声明为
private final,确保异常抛出后状态不可变 - 提供
getErrorCode()、getContext()等 getter,不暴露 setter


















