业务异常怎么返回统一的错误体?ProblemDetail 是什么、怎么开?多个 @ControllerAdvice 谁先谁后?为什么我的 @ResponseStatus 不生效了?Filter 里的异常谁来管?这篇在 Spring Boot 4.0.8 的 Spring MVC 7.0.9 上逐个验证。
GET /e/illegal → 500 [application/json] {"status": 500, "error": "Internal Server Error", "path": "/e/illegal"}
GET /e/rse → 418 [application/json] {"status": 418, "error": "I'm a teapot", "path": "/e/rse"}
GET /e/annotated → 409 [application/json] {"status": 409, "error": "Conflict", "path": "/e/annotated"}
GET /e/biz → 500 [application/json] {"status": 500, "error": "Internal Server Error", "path": "/e/biz"}
GET /e/arg → 422 [text/plain] [控制器内的 @ExceptionHandler] 参数不合法
GET /e/param → 400 [application/json] {"status": 400, "error": "Bad Request", "path": "/e/param"}
GET /nope → 404 [application/json] {"status": 404, "error": "Not Found", "path": "/nope"}
GET /e/illegal [头 X-Boom] → 500 [application/json] {"status": 500, "error": "Internal Server Error", "path": "/e/illegal"}Boot 的默认错误 JSON 只有状态码、错误名和路径,业务异常信息丢失。
GET /e/illegal → 500 [application/json] {"status": 500, "error": "Internal Server Error", "path": "/e/illegal"}
GET /e/rse → 418 [application/problem+json] {"detail": "我是茶壶", "status": 418, "title": "I'm a teapot"}
GET /e/annotated → 409 [application/json] {"status": 409, "error": "Conflict", "path": "/e/annotated"}
GET /e/biz → 500 [application/json] {"status": 500, "error": "Internal Server Error", "path": "/e/biz"}
GET /e/arg → 422 [text/plain] [控制器内的 @ExceptionHandler] 参数不合法
GET /e/param → 400 [application/problem+json] {"detail": "Required parameter 'q' is not present.", "status": 400, "title": "Bad Request"}
GET /nope → 404 [application/problem+json] {"detail": "No static resource nope.", "status": 404, "title": "Not Found"}
GET /e/illegal [头 X-Boom] → 500 [application/json] {"status": 500, "error": "Internal Server Error", "path": "/e/illegal"}开关只管标准 MVC 异常;自己的异常用 ProblemDetail 返回并可加自定义字段。
GET /e/illegal → 500 [text/plain] [Order(1) 的另一个 Advice 抢先处理了 IllegalStateException] 非法状态
GET /e/rse → 418 [application/problem+json] {"detail": "我是茶壶", "status": 418, "title": "I'm a teapot"}
GET /e/annotated → 500 [text/plain] [Advice 里 RuntimeException 兜底] ConflictEx: null
GET /e/biz → 422 [application/problem+json] {"detail": "业务异常:余额不足", "status": 422, "title": "业务错误", "type": "https://example.com/errors/biz", "code": 1001}
GET /e/arg → 422 [text/plain] [控制器内的 @ExceptionHandler] 参数不合法
GET /e/param → 400 [application/problem+json] {"detail": "Required parameter 'q' is not present.", "status": 400, "title": "Bad Request"}
GET /nope → 404 [application/problem+json] {"detail": "No static resource nope.", "status": 404, "title": "Not Found"}
GET /e/illegal [头 X-Boom] → 500 [application/json] {"status": 500, "error": "Internal Server Error", "path": "/e/illegal"}@Order 小的先处理,控制器内优先;兜底处理器会吞掉 @ResponseStatus。
GET /e/inhandler(UnsupportedOperationException → Advice 兜底处理器里又抛了 IllegalStateException)→ 500 {"timestamp":"2026-10-19T03:33:21.079Z","status":500,"error":"Internal Server Error","path":"/e/inhandler"}
GET /e/local(控制器内有 IllegalArgumentException 处理器,Advice 里也有)→ 422 [控制器内的 @ExceptionHandler] 本地处理器应该处理我处理器里再抛异常、Filter 里的异常都不经过 Advice。