自定义异常设计应聚焦定位问题、统一处理、友好提示,关键属性包括:带业务语义的错误码(如PAY-GATEWAY-TIMEOUT)、结构化上下文数据、区分检查型与非检查型、提供静态工厂方法。

自定义异常的属性设计,关键不在“多”,而在“有用”——要能支撑定位问题、统一处理、友好提示这三个实际目标。
带业务语义的错误码
错误码不是随便编个数字,而是要有层级和含义。比如 USER-404 表示用户未找到,ORDER-VALIDATE-002 表示订单校验失败中的“收货地址为空”。它应该可读、可分类、可检索,方便日志筛选、监控告警或前端映射提示文案。
- 建议用短横线分段:模块-子域-序号(如 PAY-GATEWAY-TIMEOUT)
- 避免纯数字码(如 1001),难以维护和协作
- 在基类中声明
errorCode字段,并提供 getter,确保所有子异常都可统一提取
携带上下文数据,不止于 message
仅靠 getMessage() 很难还原现场。实用的自定义异常应支持附带结构化上下文,比如用户ID、订单号、参数值等。
- 在构造时接受 Map
或专用上下文对象(如 ValidationContext) - 避免把敏感信息(密码、token)直接塞进异常字段
- 重写
toString()或新增toLogString()方法,安全地输出调试所需字段
区分检查型与非检查型,明确传播意图
继承 Exception 还是 RuntimeException,本质是回答“这个异常该由谁处理、何时处理”。
立即学习“Java免费学习笔记(深入)”;
- 业务规则类异常(如余额不足、状态非法)适合非检查型(
RuntimeException子类),调用方无需强制 try-catch,靠全局处理器统一响应 - 外部系统契约类异常(如第三方接口返回格式错误且需重试)可设计为检查型,强制上层决定重试、降级或转抛
- 不建议混合使用;一个项目内保持策略一致,否则会增加理解和维护成本
提供静态工厂方法,降低使用门槛
让业务代码抛异常像调用工具方法一样简洁,而不是每次 new + 拼字符串。
- 例如:
UserException.userNotFound("U12345")直接返回预设好 errorCode 和 message 的实例 - 支持链式传参:
OrderException.invalidStatus(orderId, "PAID", "SHIPPED") - 工厂方法内部完成上下文组装和日志埋点准备,避免重复逻辑散落在各处


















