samanime
samanime

Reputation: 26537

Spring Global Exception Handler - Not MVC

Does regular Spring (Boot) have a way to do global exception handling, or at least catch any uncaught exceptions (such as RuntimeException that may happen randomly)?

I've googled around, but everything I've found talked about the @ControllerAdvice @ExceptionHandler for controllers. Nothing about just a general global exception handler.

I basically just want to make sure that if some exception happens that I'm not already catching, that I log it so I know about it.

Upvotes: 17

Views: 6823

Answers (3)

RZet
RZet

Reputation: 1074

Best to extend Spring's ResponseEntityExceptionHandler class and customise it in a way you want, for example:

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import javax.validation.ValidationException;

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(ValidationException.class)
    protected ResponseEntity<Object> handle(ValidationException ex) {
        return new ResponseEntity<>(getError(HttpStatus.BAD_REQUEST, ex), new HttpHeaders(), HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler
    protected ResponseEntity<Object> handle(Exception ex, WebRequest request) {
        try {
            return super.handleException(ex, request);
        } catch (Exception e) {
            return handleExceptionInternal(ex, null, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
        }
    }

    @Override
    protected ResponseEntity<Object> handleExceptionInternal(Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
        if (HttpStatus.Series.SERVER_ERROR == status.series()) {
            log.error("Unexpected error.", ex);
        }
        return new ResponseEntity<>(getError(status, ex), headers, status);
    }

    private ApiError getError(HttpStatus status, Exception ex) {
        return ApiError.builder()
            .status(status.value())
            .message(ex.getMessage())
            .build();
    }
}

This way you can override Spring's default error response body, declare your custom exceptions (e.g. ValidationException) as well as allow Spring to handle its default exceptions declared in ResponseEntityExceptionHandler.

Note although try-catch around super.handleException(ex, request) is not required in Spring 4.x, it is necessary in Spring 5.x due to a change of the underlying ResponseEntityExceptionHandler implementation.

Upvotes: -2

Essex Boy
Essex Boy

Reputation: 7950

Add your own via AOP, something like this will work:

@Aspect
@Component
public class ExceptionHandler {

    @AfterThrowing(pointcut = "within(*.*)", throwing = "t")
    public void log(Throwable t) {
        t.printStackTrace();
    }
}

Look at the cheat sheet to help with the pointcut.

Upvotes: 2

pedrohreis
pedrohreis

Reputation: 1090

I think spring does not provide an out of the box solution for this.

A possibility to achieve this is using AOP with annotations (you may find an example here: Where can I catch non rest controller exceptions in spring?). With this approach, you can define a global pointcut for all the methods annotated with a pre-defined annotation. The good part of this approach is to have all the error handling logic in one place.

Upvotes: 4

Related Questions