Simplifying Spring (Boot) ExceptionHandlers with ResponseStatus Annotations

Featured image for sharing metadata for article

Mapping exceptions to HTTP errors in Spring is pretty handily done using the ExceptionHandler and ControllerAdvice mechanisms. But, if you're using quite a few exceptions that can be mapped to HTTP errors, you may find that you're having a fair bit of overhead for managing common exception cases, and I have seen it before that we may want to simplify the setup.

One option we can use is by combining the ExceptionHandler into a single handler for the Exception class. Minor caveat to mention that you won't include all Spring-handled exceptions - see Globally Logging all Spring (Boot) Exceptions for how to add them.

This solution allows us to annotate our exceptions, as such:

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

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
  public BadRequestException(String message) {
    super(message);
  }
}

Which can then be mapped using our exception handler:

import org.springframework.core.annotation.AnnotationUtils;
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.bind.annotation.ResponseStatus;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<Object> handleAllUnmatchedExceptions(Exception ex) {
        ResponseStatus responseStatus =
                AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);
        if (responseStatus == null || HttpStatus.INTERNAL_SERVER_ERROR.equals(responseStatus.code())) {
          // for instance, we may only want 500s to be an empty body
          return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
        return new ResponseEntity<>(ex.getMessage(), responseStatus.code());
    }
}

This also works with subclasses, as they inherit the annotations of a parent class.

Note that this is not without tradeoffs - I find it's easier to be explicit about the handlers and routes that things can happen, and it also will make it more difficult for your unit testing. It's also using Reflection which will slow the response down a little bit more than without using AnnotationUtils.

Alternatively, you may want to extend ResponseStatusException.

Written by Jamie Tanna's profile image Jamie Tanna on , and last updated on .

Content for this article is shared under the terms of the Creative Commons Attribution Non Commercial Share Alike 4.0 International, and code is shared under the Apache License 2.0.

#blogumentation #java #spring-boot #spring.

This post was filed under articles.

Interactions with this post

Interactions with this post

Below you can find the interactions that this page has had using WebMention.

Have you written a response to this post? Let me know the URL:

Do you not have a website set up with WebMention capabilities? You can use Comment Parade.