Global Exception Handling with @ControllerAdvice in Spring Boot

Error handling is an essential part of building robust and user-friendly applications. Without proper exception management, users could be greeted with cryptic error messages or unclear feedback, leading to frustration and mistrust. Enter @ControllerAdvice, a powerful Spring Boot feature that facilitates centralized and global exception handling in your web applications.

This guide explores how @ControllerAdvice helps you manage exceptions across your Spring Boot application. We’ll highlight its features, share best practices, and compare it to local exception handling methods. Additionally, you’ll see a practical implementation with a custom exception and ResponseEntity.

By the end of this post, you’ll be equipped to streamline exception handling and enhance your application’s resilience and usability.

Table of Contents

  1. What is @ControllerAdvice in Spring Boot?
  2. Centralized Exception Logic
  3. Example with Custom Exception and ResponseEntity
  4. Benefits of Global vs Local Handling
  5. Summary

What is @ControllerAdvice in Spring Boot?

Definition

@ControllerAdvice is a Spring annotation that allows you to handle exceptions across your entire application in one central place. Rather than scattering exception handling logic across multiple controllers, @ControllerAdvice enables a clean separation of concerns by centralizing error handling into a dedicated class.

How Does It Work?

When an exception occurs, Spring scans for any @ExceptionHandler methods defined within a @ControllerAdvice class. If it finds a matching handler for the exception type, it executes that method, returning the defined response to the user.

Here’s the basic syntax for a @ControllerAdvice class:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleAllExceptions(Exception ex) {
        return new ResponseEntity<>("An error occurred.", HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

Key Features

  • Global Scope: Applies to all controllers within the application.
  • Flexibility: Capable of handling specific or generic exceptions.
  • Custom Responses: Lets you define meaningful responses tailored to each exception type.

Whether you’re working on a small project or a large-scale application, @ControllerAdvice streamlines error management and improves code maintainability.


Centralized Exception Logic

One of the primary advantages of using @ControllerAdvice is the ability to centralize your exception-handling logic. This section examines why centralized handling is a game changer.

Local Handling vs Centralized Handling

By default, Spring Boot applications can handle exceptions locally within individual controllers through the use of the @ExceptionHandler annotation. While this approach works for specific use cases, it can lead to duplicating similar exception-handling code across multiple controllers.

Example of Local Handling:

@RestController
public class ProductController {

    @GetMapping("/{id}")
    public Product getProduct(@PathVariable Long id) {
        if (id < 1000) {
            throw new IllegalArgumentException("Invalid Product ID");
        }
        return productService.findProductById(id);
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
    }
}

While this works for handling exceptions for each controller, it becomes unwieldy for applications with many different controllers and shared exception types.

With @ControllerAdvice, you can extract all exception-handling logic into one global class.

Centralized Logic in Practice

Here’s how we can consolidate the previous exception-handling example using @ControllerAdvice:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
    }
}

This class now handles IllegalArgumentException for all controllers, reducing redundancy and ensuring consistent error responses across your application.

Benefits of Centralized Exception Handling

  1. Consistency: Ensures all exceptions are handled in the same way, improving user experience.
  2. Maintainability: Reduces duplicate code by centralizing error management.
  3. Scalability: Easier to add or modify exception-handling logic as your application grows.

By centralizing exception handling, you maintain cleaner code and a better user interface.


Example with Custom Exception and ResponseEntity

Handling exceptions becomes even more impactful when paired with custom exceptions and comprehensive JSON responses. This section illustrates how to craft a custom exception and respond with a well-structured JSON body using ResponseEntity.

Step 1: Define a Custom Exception

First, create a custom exception class for a specific use case:

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

Step 2: Handle the Exception Globally

Next, define a method in your @ControllerAdvice class to handle the ResourceNotFoundException:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<Map<String, Object>> handleResourceNotFound(ResourceNotFoundException ex) {
        Map<String, Object> errorDetails = new HashMap<>();
        errorDetails.put("timestamp", LocalDateTime.now());
        errorDetails.put("message", ex.getMessage());
        errorDetails.put("status", HttpStatus.NOT_FOUND.value());

        return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
    }
}

Step 3: Trigger the Exception

Finally, throw the exception in a controller method:

@RestController
@RequestMapping("/api/resources")
public class ResourceController {

    @GetMapping("/{id}")
    public Resource getResource(@PathVariable Long id) {
        if (id > 1000) {
            throw new ResourceNotFoundException("Resource with ID " + id + " not found.");
        }
        return resourceService.findById(id);
    }
}

Sample JSON Response

When the ResourceNotFoundException is triggered, the client receives a structured JSON response:

{
  "timestamp": "2025-06-12T18:45:12",
  "message": "Resource with ID 1500 not found.",
  "status": 404
}

This response is both readable and helpful for users and developers.


Benefits of Global vs Local Handling

Both global (@ControllerAdvice) and local (@ExceptionHandler) handling provide value in different scenarios. Here’s a comparison to help you choose the right approach.

FeatureGlobal Handling (@ControllerAdvice)Local Handling (@ExceptionHandler)
ScopeApplies to all controllers.Applies to a single controller.
Code DuplicationEliminates repeated code.May lead to duplicate handling logic.
MaintainabilityEasier to manage and update.Less maintainable for large applications.
Use CaseShared exceptions across the app.Controller-specific exceptions.

When to Use Each

  • Global Handling:
    • Suitable for shared exception logic (e.g., NullPointerException, ResourceNotFoundException).
    • Ideal for large, multi-controller applications.
  • Local Handling:
    • Best for controller-specific exceptions.
    • Useful for exceptions that require unique responses per controller.

By combining both approaches thoughtfully, you can strike a balance between flexibility and simplicity.


Summary

Exception handling is a crucial feature for any application, ensuring errors are managed predictably and users receive meaningful feedback. Here’s what we discussed:

  1. What is @ControllerAdvice?: It centralizes exception-handling logic, ensuring consistent application-wide error responses.
  2. Centralized Exception Logic: Organize all error-handling logic centrally, reducing code duplication and increasing maintainability.
  3. Custom Exception and ResponseEntity: Create specific exceptions and provide detailed JSON responses for improved clarity.
  4. Global vs Local Handling: Understand when to use global exception handling (@ControllerAdvice) versus local handling (@ExceptionHandler).

By adopting @ControllerAdvice, you equip your application with scalable and maintainable exception-handling capabilities. Start implementing it today to build more robust and user-friendly Spring Boot applications!

Similar Posts

Leave a Reply