Aggregate exceptions in Spring with ControllerAdvice

Posted on Jan 21, 2024

Exceptions can affect the readability of code in a negative way. One way to mitigate this is to handle exceptions in one central place.

In Spring Boot you can use @ControllerAdvice for this.

I’ll show an example, but keep in mind that there’s more than shown here. So, read up the Javadoc for ControllerAdvice to get more information on the features and ways to configure it.

The problem

Let’s say the following exception is thrown anywhere in the code:

public class MissingValueException extends RuntimeException {
    public MissingValueException() {
        super();
    }

    public MissingValueException(String message) {
        super(message);
    }
}

It’s possible to ignore it as it’s a RuntimeException. But to show the client a good HTTP response code it’s recommended to handle this exception and react to it properly.

It would be possible to catch it in the top executing method. For example in the GET request handler of the controller:

@GetMapping
public ResponseEntity<String> getUser() {
    try {
        missingValueException();
    } catch(MissingValueException ex) {
        return ResponseEntity.ok().build();
    }

    return ResponseEntity.ok().build();
}

The disadvantage of it:

  • This works not so nice for RuntimeException as the caller of the methods doesn’t know which exceptions can be throw. This looks different for an Exception, but anyway what to do with the RuntimeExceptions?
  • The code gets unreadable fast.
  • Developers will need to handle the same exception with the same response code in multiple places.

So, it’s better to handle this in one central place.

The solution

Consider the following GetMapping and method which throws the MissingValueException by purpose:

@GetMapping
public String get() {
    missingValueException();
    return "Not reachable.";
}

private void missingValueException() throws MissingValueException {
    throw new MissingValueException("Missing value!");
}

When executing it, Spring Boot will return a 500 status code.

To provider proper responses, create a class which is annotated with the mentioned @ControllerAdvice.

This class also contains an @ExceptionHandler which is handling the MissingValueException and returning a proper ResponseEntity:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler
    public ResponseEntity<String> handleException(Exception ex, WebRequest request) {
        if (ex instanceof MissingValueException) {
            System.err.println("Got an error: " + ex.getMessage());
            return ResponseEntity.status(400).body("Missing data.");
        }

        System.err.println("Got an error: " + ex.getMessage());
        return ResponseEntity.internalServerError().body("500 Internal Server Error");
    }

}

The code above shows also the fallback path which is returning an 500 Internal Server Error.

And that is the global handler to handle exceptions!

Other styles of the handleException method

The handleException method can have different styles defined by the annotations.

The @ExceptionHandler annotation has a parameter for the supported exceptions of the handler. Other exceptions will either return a 500 Internal Server Error or need to be handled by a different handler.

The ResponseStatus defines the default error code to be return when handling the exception. Both annotations combined you can think of many different ways to handle exceptions.

For example an annotation-driven handler:

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MissingValueException.class)
public void handleBadRequest() {
    // nothing to do here
}

Or also an annotation-driven handler, which inspects the exception and request:

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MissingValueException.class)
public void handleBadRequest(Exception ex, WebRequest request) {
    // inspect exception/request
}

Conclusion

This post showed how handling exceptions centrally using @ControllerAdvice in Spring Boot can significantly enhance code readability and maintainability.

By consolidating exception handling in one place, developers can avoid code duplication, improve response consistency, and ensure a more systematic approach to dealing with exceptions throughout their applications.