CodeGym /Courses /Module 5. Spring /Global error handling with @ControllerAdvice

Global error handling with @ControllerAdvice

Module 5. Spring
Level 8 , Lesson 5
Available

Let's take a step forward and dive into global error handling with Spring's powerful tool — @ControllerAdvice. In this lecture you'll see how to centralize error handling across your app, making it more flexible and nicer.


What is @ControllerAdvice?

@ControllerAdvice is a Spring Framework annotation that lets you centrally manage exception handling for all controllers in your application. If before you were handling exceptions locally in a single controller with @ExceptionHandler, with @ControllerAdvice you can move that error-handling logic to a separate class that applies to every controller.

Imagine you have an app with lots of controllers where different endpoints can throw the same exceptions (for example, MethodArgumentNotValidException on validation errors). If you write handlers in every controller, you'll end up duplicating code and it becomes hard to maintain. @ControllerAdvice fixes that: declare a global handler once and unify your error-handling logic.

How does it work?

Spring automatically registers classes annotated with @ControllerAdvice and applies them to all controllers in your app. You can combine this with @ExceptionHandler to define which exceptions to handle.


How to set up a global handler using @ControllerAdvice?

Step 1. Create a class annotated with @ControllerAdvice

Create a new class that will handle exceptions globally. For example, let's call it GlobalExceptionHandler.


package com.example.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    // Handle general exception
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGenericException(Exception ex) {
        return new ResponseEntity<>("An error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

What's happening here?

  • We use @ControllerAdvice to tell Spring that this class will handle exceptions for all controllers.
  • In handleGenericException we catch any exception (Exception.class) and return a simple error message with HTTP status 500 Internal Server Error.

Step 2. Handle specific exceptions

Often you don't want to handle every exception the same way, but specific types — e.g. validation errors or resource-not-found situations. Let's add handlers for those cases.


import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.http.HttpStatus;

@ControllerAdvice
public class GlobalExceptionHandler {

    // Handle validation errors
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException ex) {
        return new ResponseEntity<>("Validation error: check the provided data", HttpStatus.BAD_REQUEST);
    }

    // Handle resource not found
    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResponseEntity<String> handleResourceNotFoundException(ResourceNotFoundException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }
}

Note:

  • To handle validation errors we added a handler that catches MethodArgumentNotValidException. This exception is generated automatically when using @Valid.
  • For resource errors we assume there's a custom ResourceNotFoundException. It's an example of a custom exception that can be thrown from the service layer.

Step 3. Create a custom exception

If you have situations that need your own handling logic, it's better to create a custom exception. Here's an example:


package com.example.demo.exception;

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

Now you can throw this exception, for example, in a service:


if (resource == null) {
    throw new ResourceNotFoundException("Resource with ID " + id + " was not found");
}

Example: Combined error handling

Let's put it all together. Suppose we have a simple controller with a method to get a resource by ID. If the resource isn't found we throw ResourceNotFoundException. If the data is invalid, Spring will automatically throw MethodArgumentNotValidException.

Controller


package com.example.demo.controller;

import com.example.demo.exception.ResourceNotFoundException;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.Min;

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

    @GetMapping("/{id}")
    public String getResource(@PathVariable @Min(1) Long id) {
        // Simulate resource lookup
        if (id == 999) {
            throw new ResourceNotFoundException("Resource with ID " + id + " was not found");
        }
        return "Resource found: ID = " + id;
    }
}

Notes:

  • We use the @Min(1) annotation to validate the ID. If ID is less than 1, Spring will automatically throw MethodArgumentNotValidException.
  • If the resource with ID 999 isn't found, our custom exception is thrown.

Results

  1. If ID is less than 1:
    
    HTTP 400 BAD REQUEST
    Validation error: check the provided data
    
  2. If the resource is not found (ID = 999):
    
    HTTP 404 NOT FOUND
    Resource with ID 999 was not found
    
  3. If any other exception happens:

    
    HTTP 500 INTERNAL SERVER ERROR
    An error occurred: [exception message]
    

Benefits of using @ControllerAdvice

  1. Centralized error handling: all error-handling code lives in one place, which makes maintaining the app easier.
  2. Consistency: errors are handled the same way everywhere, which makes your API more predictable and friendlier for clients.
  3. Flexibility: you can add handlers for different exception types and customize them to your needs.

Improving user experience

Global error handling isn't just about making the developer's life easier — it's about making your API or web app nicer for users. You can make error messages more informative, localize them to support multiple languages, and log errors for analysis and fixing issues.

In the next lectures we'll cover these topics: how to craft user-friendly error messages, how to localize them, and how to implement effective logging.

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION