<link rel="stylesheet" href="styles-3GBANQL6.css">
← Back to blog

Spring Boot

Spring Boot Global Error Handling

Spring Boot Global Error Handling

Introduction

In any Spring Boot application, handling errors gracefully is crucial for providing a good user experience and maintaining a robust backend. Spring Boot offers various ways to manage exceptions globally, ensuring that your application remains stable while giving users informative feedback. In this article, we will explore how to configure global error handling in Spring Boot, along with real-world examples of handling different types of exceptions.

Why Use Global Error Handling?

Instead of handling exceptions in each individual controller method, a global error handler allows you to define a centralized mechanism for dealing with all exceptions across the application. This not only simplifies the code but also ensures consistency in how errors are presented to the user.

Benefits of Global Error Handling

  • Consistency: Ensures uniform error responses throughout the application.
  • Separation of Concerns: Keeps error handling logic separate from business logic.
  • Maintainability: Easier to maintain as you don’t need to duplicate error-handling code in every controller.
  • Centralized Logging: Provides a centralized point for logging and tracking application issues.

How to Configure Global Error Handling in Spring Boot

Spring Boot provides the @ControllerAdvice annotation for handling exceptions globally across all controllers. By combining this with @ExceptionHandler, you can catch specific exceptions and return appropriate HTTP responses.

Step 1: Create a Global Exception Handler Class

To get started, create a class annotated with @ControllerAdvice. This class will intercept exceptions thrown by any controller in your application.

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.context.request.WebRequest;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleResourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
        ErrorResponse errorResponse = new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage());
        return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex, WebRequest request) {
        ErrorResponse errorResponse = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An unexpected error occurred");
        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

In this example:

  • The method handleResourceNotFoundException catches custom exceptions like ResourceNotFoundException and returns a 404 error response.
  • The method handleGlobalException acts as a catch-all for other exceptions, returning a 500 internal server error response.

Step 2: Create a Custom Error Response Class

To provide a consistent error message format, you can create a custom error response class that contains fields like the HTTP status code, message, and timestamp.

import java.time.LocalDateTime;

public class ErrorResponse {
    private int statusCode;
    private String message;
    private LocalDateTime timestamp;

    public ErrorResponse(int statusCode, String message) {
        this.statusCode = statusCode;
        this.message = message;
        this.timestamp = LocalDateTime.now();
    }

    // Getters and Setters
}

This class will ensure that all error responses have a consistent format with fields like statusCode, message, and timestamp.

Real-World Examples of Global Error Handling

Example 1: Handling a Resource Not Found Exception

Consider a REST API where users can fetch details of a specific resource by its ID. If the resource with the given ID does not exist, we should return a 404 error.

First, define a custom exception:

public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

Then, throw this exception from your service or controller when a resource is not found:

@GetMapping("/resource/{id}")
public ResponseEntity<Resource> getResourceById(@PathVariable Long id) {
    Resource resource = resourceService.findById(id).orElseThrow(() ->
        new ResourceNotFoundException("Resource not found with id: " + id));
    return ResponseEntity.ok(resource);
}

When this exception is thrown, the global error handler catches it and returns a 404 error response.

Example 2: Handling Validation Errors

In a real-world application, you often need to validate incoming data. If the validation fails, Spring Boot automatically throws MethodArgumentNotValidException. You can handle this exception globally as follows:

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {
    String errorMessage = ex.getBindingResult().getFieldError().getDefaultMessage();
    ErrorResponse errorResponse = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), errorMessage);
    return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}

This handler extracts the validation error message and returns a 400 bad request response.

Example 3: Handling Authentication and Authorization Errors

When dealing with authentication and authorization in a Spring Boot application, exceptions such as AccessDeniedException or AuthenticationException may be thrown. You can handle these exceptions globally to provide user-friendly messages or custom responses for API clients.

@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ErrorResponse> handleAccessDeniedException(AccessDeniedException ex) {
    ErrorResponse errorResponse = new ErrorResponse(HttpStatus.FORBIDDEN.value(), "Access Denied");
    return new ResponseEntity<>(errorResponse, HttpStatus.FORBIDDEN);
}

This handler catches access-related errors and responds with a 403 status code.

Improving Global Error Handling

1. Logging Exceptions for Monitoring

While handling exceptions globally, it is crucial to log them for auditing and monitoring purposes. Tools like Spring Boot Actuator or external monitoring systems (e.g., Logstash, ELK Stack) can be integrated for better observability.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex, WebRequest request) {
        logger.error("An error occurred: {}", ex.getMessage());
        ErrorResponse errorResponse = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An unexpected error occurred");
        return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

By logging the error message and stack trace, you can track and diagnose issues more effectively.

2. Returning JSON Error Responses for APIs

In modern web applications, especially when building APIs, it’s often better to return a structured JSON response for errors rather than a simple HTML page. This makes it easier for frontend applications, such as Angular or React, to parse and display error messages.

3. Custom Error Pages for Web Applications

If you are building a web application with a UI, you can customize error pages for different status codes. Spring Boot allows you to create error.html templates that display user-friendly messages.

LET'S BUILD WHAT'S NEXT

Ready to turn your idea into a real solution?

Let's discuss how we can help you create value with technology.

Let's Talk