spring boot全局异常处理失效的四大陷阱:一是异常类型未精确匹配继承链,需明确指定或慎用throwable;二是filter、interceptor及异步/定时任务异常不被拦截,需单独处理;三是responsebodyadvice干扰异常响应格式,需在supports中排除异常类;四是spring boot 3+内容协商导致html错误页优先,应禁用whitelabel并返回responseentity或加@responsebody。

Spring Boot 全局异常处理看似简单,写个 @RestControllerAdvice 再配几个 @ExceptionHandler 就完事——但线上一出问题,往往发现异常根本没进你的处理器,反而返回了 Whitelabel Error Page、裸堆栈或格式错乱的 JSON。这不是代码没写,而是掉进了几个隐蔽又高频的坑里。
陷阱一:异常类型没匹配上,处理器形同虚设
最常见的失效场景是:抛的是 NullPointerException,但你的 @ExceptionHandler(Exception.class) 没生效,或者只写了 @ExceptionHandler(BusinessException.class),结果数据库连接超时(SQLException)直接穿透出去变成 500。
- Spring 的异常匹配是**精确继承链匹配**,不是“兜底所有”;
Exception能捕获大多数,但像Error(如OutOfMemoryError)或某些 Spring 内部包装异常(如ResponseStatusException)可能绕过它 - 明确列出你真正要处理的类型,比如:
@ExceptionHandler({IllegalArgumentException.class, IllegalStateException.class}) - 若需兜底,用
@ExceptionHandler(Throwable.class)(注意:慎用,避免吞掉系统级致命错误)
陷阱二:Controller 层之外的异常被忽略
全局异常处理器默认只拦截 @Controller 和 @RestController 方法抛出的异常。以下情况它完全不介入:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- Filter、Interceptor 中抛出的异常(例如登录校验失败 throw new RuntimeException("未登录"))
- 异步方法(
@Async)里的异常,会抛给线程池,不会到达 MVC 异常处理器 - 定时任务(
@Scheduled)中的异常,默认静默吞掉,需单独配置TaskScheduler的异常处理器
解决办法:对 Filter/Interceptor 自行 try-catch + 手动封装响应;异步任务建议用 Future.get() 显式捕获,或配置 @Async 的异常回调。
陷阱三:ResponseBodyAdvice 干扰了异常响应格式
如果你同时用了 @RestControllerAdvice 和 ResponseBodyAdvice(比如统一加签名、加密、时间戳),要注意执行顺序:
-
ResponseBodyAdvice的beforeBodyWrite()会在异常处理器返回值之后再执行一次 - 如果它对
Result类型做了非幂等处理(比如重复加签名),会导致异常响应结构被破坏 - 务必在
supports()方法中排除异常响应类,例如:return !clazz.isAssignableFrom(Result.class);
陷阱四:Spring Boot 3+ 的响应体协商机制导致 HTML 错误页
Spring Boot 3 默认启用 WebMvcConfigurer 的内容协商,当请求头 Accept: text/html(比如浏览器直接访问接口)时,即使你写了 @RestControllerAdvice,Spring 也可能优先走默认的 Whitelabel 错误页逻辑,而不是调用你的 JSON 处理器。
- 检查是否启用了
server.error.whitelabel.enabled=false - 确保异常处理器方法返回类型是
ResponseEntity<result></result>或明确标注@ResponseBody,避免被当作视图名解析 - 在
@ExceptionHandler方法上加@ResponseStatus注解,可强制状态码,减少协商干扰










