Exception handling is a critical part of building robust and user-friendly applications. In Spring Boot, we can handle exceptions in various ways to ensure our application remains stable and provides meaningful feedback to users. This guide will cover different strategies for exception handling, including custom exceptions, global exception handling, validation errors, and best practices for production.
1. Basics of Exception Handling
Exceptions are events that disrupt the normal flow of a program. They can be divided into:
- Checked Exceptions: Exceptions that are checked at compile-time.
- Unchecked Exceptions (Runtime Exceptions): Exceptions that occur during runtime.
-
Errors: Serious issues that applications should not handle, like
OutOfMemoryError
.
2. Custom Exception Classes
Creating custom exception classes helps in handling specific error conditions in your application.
package com.example.SpringBootRefresher.exception;
public class DepartmentNotFoundException extends RuntimeException {
public DepartmentNotFoundException(String message) {
super(message);
}
}
3. Exception Handling in Controllers
@ExceptionHandler
Annotation:
You can define methods to handle exceptions in your controller classes.
package com.example.SpringBootRefresher.controller;
import com.example.SpringBootRefresher.exception.DepartmentNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DepartmentController {
@GetMapping("/department")
public String getDepartment() {
// Simulate an exception
throw new DepartmentNotFoundException("Department not found!");
}
@ExceptionHandler(DepartmentNotFoundException.class)
public ResponseEntity<String> handleDepartmentNotFoundException(DepartmentNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
}
4. Global Exception Handling with @ControllerAdvice
To handle exceptions globally, you can use @ControllerAdvice
and a centralized exception handler.
package com.example.SpringBootRefresher.error;
import com.example.SpringBootRefresher.entity.ErrorMessage;
import com.example.SpringBootRefresher.exception.DepartmentNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice
@ResponseStatus
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(DepartmentNotFoundException.class)
public ResponseEntity<ErrorMessage> handleDepartmentNotFoundException(DepartmentNotFoundException exception, WebRequest request) {
ErrorMessage message = new ErrorMessage(
HttpStatus.NOT_FOUND.value(),
exception.getMessage(),
request.getDescription(false)
);
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(message);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorMessage> handleGlobalException(Exception exception, WebRequest request) {
ErrorMessage message = new ErrorMessage(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
exception.getMessage(),
request.getDescription(false)
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(message);
}
}
5. Creating a Standard Error Response
Define a standard error response class to structure your error messages.
package com.example.SpringBootRefresher.entity;
public class ErrorMessage {
private int statusCode;
private String message;
private String description;
public ErrorMessage(int statusCode, String message, String description) {
this.statusCode = statusCode;
this.message = message;
this.description = description;
}
// Getters and setters
public int getStatusCode() {
return statusCode;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
6. Handling Validation Errors
Spring Boot integrates well with Bean Validation (JSR-380). To handle validation errors globally, use @ControllerAdvice
.
package com.example.SpringBootRefresher.error;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.WebRequest;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
@ResponseStatus
public class ValidationExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
7. Using @ResponseStatus
for Simple Exceptions
For simple cases, you can annotate an exception class with @ResponseStatus
to specify the HTTP status code.
package com.example.SpringBootRefresher.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class DepartmentNotFoundException extends RuntimeException {
public DepartmentNotFoundException(String message) {
super(message);
}
}
8. Best Practices for Production
- Consistent Error Responses: Ensure your application returns consistent and structured error responses. Use a standard error response class.
- Logging: Log exceptions for debugging and monitoring purposes. Ensure sensitive information is not exposed in logs.
- User-Friendly Messages: Provide user-friendly error messages. Avoid exposing internal details or stack traces to users.
- Security: Be cautious about the information included in error responses to avoid exposing sensitive data.
- Documentation: Document your exception handling strategy for your team and future maintainers.
Summary
Exception handling in Spring Boot involves using annotations like @ExceptionHandler
, @ControllerAdvice
, and @ResponseStatus
to manage errors effectively. By creating custom exceptions, handling validation errors, and following best practices, you can build robust applications that handle errors gracefully and provide meaningful feedback to users. Using Java 17 features ensures your application leverages the latest improvements in the Java ecosystem.