Content-Type: application/json。@Valid 失敗):SpringMVC 預設拋出 MethodArgumentNotValidException,容器直接返回 **HTML 400 頁面**,忽略了 DefaultHandlerExceptionResolver 中寫的 JSON 邏輯。在 DefaultHandlerExceptionResolver 子類中調用 response.sendError(400) 會**觸發容器預設錯誤頁面機制**,導致響應體被容器覆蓋為 HTML,且 Content-Type 被重置。
response.setStatus(400)@ControllerAdvice
public class GlobalExceptionHandler extends DefaultHandlerExceptionResolver {
@Override
protected ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
if (ex instanceof MethodArgumentNotValidException) {
// 關鍵:setStatus 不觸發容器錯誤頁
response.setStatus(HttpServletResponse.SC_BAD_REQUEST); // 400
response.setContentType("application/json;charset=UTF-8");
try {
String json = "{\"code\":400,\"msg\":\"參數校驗失敗\",\"errors\":[...]}";
response.getWriter().write(json);
} catch (IOException e) {
logger.error("write error response failed", e);
}
return new ModelAndView(); // 返回空 MV,終止後續處理
}
return super.doResolveException(request, response, handler, ex);
}
}
| 方法 | 行為 | 適用場景 |
|---|---|---|
response.sendError(400) | 容器攔截,返回預設錯誤頁(HTML),**丟棄自定義響應體** | 真的想讓容器處理錯誤頁 |
response.setStatus(400) | **僅設置狀態碼**,不觸發容器錯誤頁,**保留自定義響應體** | REST API 返回 JSON 錯誤 |
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleValidException(MethodArgumentNotValidException ex) {
List errors = ex.getBindingResult()
.getFieldErrors().stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.collect(Collectors.toList());
return new ErrorResponse(400, "參數校驗失敗", errors);
}
}
@RestControllerAdvice = @ControllerAdvice + @ResponseBody。@ResponseStatus 直接設置狀態碼,**不走 sendError**。REST API 返回錯誤碼 + JSON 體,永遠用 response.setStatus() 或 @ResponseStatus,禁用 sendError()。這是 SpringMVC/SpringBoot 開發的鐵律。