Introduction
When building a Spring Boot application, errors and exceptions are inevitable—such as invalid data, missing records, or server failures. If each controller handles errors separately, the code becomes messy and complicated to maintain. A better approach is Global Exception Handling. Spring Boot provides powerful tools such as @ControllerAdvice and @ExceptionHandler to handle exceptions cleanly and consistently. In this article, we explain how to implement global exception handling in plain language.
Why Global Exception Handling Is Important
A good error-handling system:
Keeps the code clean
Returns consistent error messages
Improves debugging
Enhances user experience
Helps maintain clean APIs for frontend or mobile apps
Instead of writing try-catch blocks everywhere, you can manage all exceptions in one place.
Step 1: Create Custom Exception Classes
Custom exceptions make it easy to identify specific error scenarios.
Example: Exception when user is not found
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
Example: Exception for invalid input
public class InvalidInputException extends RuntimeException {
public InvalidInputException(String message) {
super(message);
}
}
Step 2: Create a Global Exception Handler Using @ControllerAdvice
@ControllerAdvice tells Spring Boot to look here for handling all exceptions.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
@ExceptionHandler(InvalidInputException.class)
public ResponseEntity<String> handleInvalidInput(InvalidInputException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneralErrors(Exception ex) {
return new ResponseEntity<>("Something went wrong!", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
This ensures every exception gets a proper and consistent response.
Step 3: Create a Standard Error Response Structure
Instead of returning plain text, send a clear JSON structure.
public class ErrorResponse {
private String message;
private LocalDateTime timestamp;
private String path;
public ErrorResponse(String message, String path) {
this.message = message;
this.timestamp = LocalDateTime.now();
this.path = path;
}
// getters & setters
}
Update your exception handler to return JSON:
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException ex, HttpServletRequest request) {
ErrorResponse response = new ErrorResponse(ex.getMessage(), request.getRequestURI());
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
Step 4: Throw Exceptions from Controller or Service
Controller:
@GetMapping("/users/{id}")
public User getUser(@PathVariable int id) {
return userService.getUserById(id);
}
Service class:
public User getUserById(int id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found with ID: " + id));
}
Now your global handler handles this error automatically.
Step 5: Handle Validation Errors
Use @Valid and handle MethodArgumentNotValidException.
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
String errorMsg = ex.getBindingResult().getFieldError().getDefaultMessage();
ErrorResponse response = new ErrorResponse(errorMsg, "Validation Error");
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
This ensures clean validation error responses.
Step 6: Handle Global Errors Using @RestControllerAdvice
@RestControllerAdvice returns JSON automatically.
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(Exception.class)
public ErrorResponse handleAll(Exception ex) {
return new ErrorResponse("Internal Server Error", "Unknown");
}
}
Real-Life Example
A travel booking company in India uses a Spring Boot backend. When a user tries to book a ticket with an invalid ID, the API returns a clean JSON message such as:
{
"message": "Booking not found",
"timestamp": "2025-01-12T10:20:30",
"path": "/bookings/99"
}
This helps frontend developers show readable error messages to the users.
Best Practices
Keep exception messages meaningful
Use custom exceptions for clarity
Never expose internal server errors to users
Log exceptions properly
Use a unified structure for all error responses

Join the conversation! Your thoughts help the community grow.