应继承Exception以强制调用方处理,适用于可预期需恢复的业务错误;继承RuntimeException用于程序bug等不可恢复错误,不强制处理。
Exception 还是 RuntimeException?关键看是否强制处理Java 自定义异常的核心判断点在于:你希望调用方**必须显式处理**这个异常,还是允许它“悄悄传播”。Exception 及其子类是检查型异常(checked),编译器强制要求 try-catch 或 throws;而 RuntimeException 是非检查型(unchecked),不强制处理。
Exception
RuntimeException,避免污染正常流程RuntimeException——这会让调用方失去对关键错误的感知能力自定义

new MyException("msg") 编译失败这类低级问题。标准做法是提供以下三个构造函数:public class InsufficientBalanceException extends Exception {
public InsufficientBalanceException() {
super();
}
public InsufficientBalanceException(String message) {
super(message);
}
public InsufficientBalanceException(String message, Throwable cause) {
super(message, cause);
}}
String 构造是日常使用频率最高的,务必支持Throwable 的构造用于链式异常封装(比如捕获 SQLException 后包装成业务异常)多数情况下,仅靠继承 + 标准构造就足够了。只有当异常需要携带额外上下文且被上层明确消费时,才考虑加字段。例如:
private final int errorCode
private final String userId
final,保证异常对象不可变常见误用:给异常加 log() 方法或 sendToMonitor() —— 这混淆了“描述错误”和“处理错误”的职责。
自定义异常的价值在于精准传达问题,但实际中常因用法不当而失效:
throw new MyException("error"),优先用带上下文的字符串:throw new MyException("转账失败:目标账户 " + accountId + " 不存在")
catch (MyException e) { /* 空 */ } —— 至少要 logger.error("", e)
throw new ServiceException("调用下游失败", e) 才能保留堆栈
@ResponseStatus),确保你的异常类满足其扫描条件(通常是 public、有默认构造)真正难的不是写一个异常类,而是整个团队对每种异常的语义、处理边界和日志规范达成一致。没约定好“这个异常谁负责兜底”,再多的自定义也没用。