乐闻世界logo
搜索文章和话题

How to implement global exception handling in Spring Boot?

3月6日 21:58

Spring Boot Global Exception Handling

Why Global Exception Handling

In web applications, exception handling is essential:

  • User Experience: Friendly error messages instead of stack traces
  • System Security: Hide internal implementation details
  • Clean Code: Avoid try-catch in every method
  • Unified Standards: Consistent error response format

Approach 1: @ControllerAdvice + @ExceptionHandler (Recommended)

1. Global Exception Handler

java
@RestControllerAdvice @Slf4j public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public ResponseEntity<Result<Void>> handleBusinessException(BusinessException e) { log.warn("Business exception: {}", e.getMessage()); return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(Result.error(e.getCode(), e.getMessage())); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Result<Void>> handleValidationException(MethodArgumentNotValidException e) { String message = e.getBindingResult().getFieldErrors().stream() .map(error -> error.getField() + ": " + error.getDefaultMessage()) .collect(Collectors.joining(", ")); return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(Result.error(400, message)); } @ExceptionHandler(Exception.class) public ResponseEntity<Result<Void>> handleException(Exception e) { log.error("System exception", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(Result.error(500, "System busy, please try again later")); } }

2. Unified Response Class

java
@Data public class Result<T> { private Integer code; private String message; private T data; private Long timestamp; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("success"); result.setData(data); return result; } public static <T> Result<T> error(Integer code, String message) { Result<T> result = new Result<>(); result.setCode(code); result.setMessage(message); return result; } }

3. Custom Business Exception

java
@Getter public class BusinessException extends RuntimeException { private final Integer code; public BusinessException(String message) { super(message); this.code = 500; } public BusinessException(Integer code, String message) { super(message); this.code = code; } }

Approach 2: Implement HandlerExceptionResolver

java
@Component @Order(Ordered.HIGHEST_PRECEDENCE) public class CustomExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { try { response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); PrintWriter writer = response.getWriter(); Result<Void> result; if (ex instanceof BusinessException) { BusinessException be = (BusinessException) ex; response.setStatus(HttpServletResponse.SC_BAD_REQUEST); result = Result.error(be.getCode(), be.getMessage()); } else { result = Result.error(500, "System error"); } writer.write(JSON.toJSONString(result)); writer.flush(); writer.close(); } catch (IOException e) { e.printStackTrace(); } return new ModelAndView(); } }

Approach 3: @ResponseStatus Annotation

java
@ResponseStatus(HttpStatus.NOT_FOUND) public class ResourceNotFoundException extends RuntimeException { public ResourceNotFoundException(String message) { super(message); } } @ResponseStatus(HttpStatus.BAD_REQUEST) public class InvalidParameterException extends RuntimeException { public InvalidParameterException(String message) { super(message); } }

Parameter Validation Exception Handling

1. Entity with Validation Annotations

java
@Data public class UserCreateDTO { @NotBlank(message = "Username cannot be empty") @Size(min = 3, max = 20, message = "Username must be 3-20 characters") private String username; @NotBlank(message = "Password cannot be empty") @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,}$", message = "Password must contain uppercase, lowercase, and number") private String password; @NotBlank(message = "Email cannot be empty") @Email(message = "Invalid email format") private String email; @Min(value = 0, message = "Age cannot be negative") @Max(value = 150, message = "Age cannot exceed 150") private Integer age; }

2. Group Validation

java
public interface CreateGroup {} public interface UpdateGroup {} @Data public class UserDTO { @NotNull(groups = UpdateGroup.class) private Long id; @NotBlank(groups = CreateGroup.class) private String username; }

3. Controller Usage

java
@RestController @RequestMapping("/users") public class UserController { @PostMapping public Result<Void> create(@Validated(CreateGroup.class) @RequestBody UserDTO dto) { return Result.success(); } @PutMapping public Result<Void> update(@Validated(UpdateGroup.class) @RequestBody UserDTO dto) { return Result.success(); } }

404 Exception Handling Configuration

Enable 404 exception throwing:

yaml
spring: web: resources: add-mappings: false mvc: throw-exception-if-no-handler-found: true
java
@RestControllerAdvice public class NotFoundExceptionHandler { @ExceptionHandler(NoHandlerFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public Result<Void> handleNoHandlerFound(NoHandlerFoundException e) { return Result.error(404, "Path not found: " + e.getRequestURL()); } }

Logging Best Practices

java
@Slf4j @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public ResponseEntity<Result<Void>> handleBusinessException(BusinessException e, WebRequest request) { log.warn("Business exception [{}] - URL: {}, Message: {}", e.getCode(), request.getDescription(false), e.getMessage()); return ResponseEntity.badRequest() .body(Result.error(e.getCode(), e.getMessage())); } @ExceptionHandler(Exception.class) public ResponseEntity<Result<Void>> handleException(Exception e, WebRequest request) { String traceId = UUID.randomUUID().toString(); log.error("System exception [TraceId: {}] - URL: {}", traceId, request.getDescription(false), e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(Result.error(500, "System busy [TraceId: " + traceId + "]")); } }

Summary

ApproachUse CaseProsCons
@ControllerAdviceMost scenariosCentralized, flexibleNone
HandlerExceptionResolverFull control neededHighest priorityComplex
@ResponseStatusSimple exceptionsConciseLess flexible

Global exception handling is fundamental for building robust REST APIs. Recommendations:

  1. Unified error response format
  2. Clear error code system
  3. Proper logging (distinguish warn/error)
  4. Hide sensitive information in production
标签:Spring Boot