Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where to handle Exceptions in Spring Applications

I'm still learning Spring. I have an Application set upped and i'm trying to understand exception handling in Spring.

I'm using @ControllerAdvice to handle exceptions. In my application there are few layers such as Services, Controllers, Models and Repositories. In which Layer should i be handling my exceptions? Or should i be handling exception in each layer as appropriate?

like image 381
Harsha Jayamanna Avatar asked Jul 11 '17 12:07

Harsha Jayamanna


People also ask

What is exception handling in Spring Boot?

Spring Boot is a microservice-based framework and making a production-ready application in it takes very little time. Exception Handling in Spring Boot helps to deal with errors and exceptions present in APIs so as to deliver a robust enterprise application.

What is the best way to handle exceptions in an application?

Throwing the exception is also a costly operation. It a good option that catch the exception at service layer itself and based on the exception, generate the response and sent it to the controller. While developing the application, the deveoper has always has better idea what type of exception he/she has to deal with.

What is exception handling in spring Bool RESTful?

When you develop a Spring Bool RESTful service, you as a programmer are responsible for handling exceptions in the service. For instance, by properly handling exceptions, you can stop the disruption of the normal flow of the application. In addition, proper exception handling ensures that the code doesn’t break when an exception occurs.

What are exceptions thrown outside of the spring mvc framework?

Exceptions thrown outside the Spring MVC framework, such as from a servlet Filter, are still reported by the Spring Boot fallback error page. The sample application also shows an example of this.


2 Answers

This is a good way to start your Exception handling in Spring:

Step 1 - Create a specific DefaultExceptionHandler class, and annotate it using the @ControllerAdvice annotation. In this handler class, you have different methods, catching both expected and unexpected exceptions, which are annotated using the @ExceptionHandler annotation:

@ControllerAdvice("com.stackoverflow.example")
@SuppressWarnings("WeakerAccess")
public class DefaultExceptionHandler extends ResponseEntityExceptionHandler {

    private final Logger log = LoggerFactory.getLogger("DefaultExceptionHandler");

    private final MessageSourceAccessor messageSource;

    @Autowired
    public DefaultExceptionHandler(MessageSourceAccessor messageSource) {
        Assert.notNull(messageSource, "messageSource must not be null");
        this.messageSource = messageSource;
     }

      @ExceptionHandler(ApplicationSpecificException.class)
      public ResponseEntity<Object> handleApplicationSpecificException(ApplicationSpecificExceptionex) {
         final Error error = buildError(ex);
         return handleExceptionInternal(ex, ex.getHttpStatus(), error);
      }

       @ExceptionHandler(Exception.class)
       public ResponseEntity<Object> handleException(Exception ex) {
           final Error error = buildError(ex);
           return handleExceptionInternal(ex, HttpStatus.INTERNAL_SERVER_ERROR, error);
    }
}

Step 2 - Create an application specific exception (ApplicationSpecificException class) used for expected exceptions and throw this exception on any level and it will get picked up by Spring:

public class ApplicationSpecificException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    private final ExceptionType exceptionType;

    public ApplicationSpecificException(ExceptionType exceptionType, Object... messageArguments) {
        super(MessageFormat.format(exceptionType.getMessage(), messageArguments));
        this.exceptionType = exceptionType;
    }

    public ApplicationSpecificException(ExceptionType exceptionType, final Throwable cause, Object... messageArguments) {
        super(MessageFormat.format(exceptionType.getMessage(), messageArguments), cause);
        this.exceptionType = exceptionType;
    }

    public HttpStatus getHttpStatus() {
        return exceptionType.getStatus();
    }

    public ExceptionType getExceptionType() {
        return exceptionType;
    }
}

With ExceptionType being an enum:

public enum ExceptionType {

    HTTP_INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "An internal server error occurred.");
    //you can specify your own exception types...

    private HttpStatus status;
    private String message;

    ExceptionType(HttpStatus status, String message) {
        this.status = status;
        this.message = message;
    }

    public HttpStatus getStatus() {
        return status;
    }

    public String getMessage() {
        return message;
    }
}

Step 3 - Finally, created an ExceptionFactory class. This allows you to automatically log the exception in your application logs:

public class ExceptionFactory {

    private static final Logger LOG = LoggerFactory.getLogger(ExceptionFactory.class);

    public static ApplicationSpecificException create(final Throwable cause, final ExceptionType exceptionType, final Object... messageArguments) {
        LOG.error(MessageFormat.format(exceptionType.getMessage(), messageArguments), cause);
        return new ApplicationSpecificException (exceptionType, cause, messageArguments);
    }

    public static ApplicationSpecificException create(final ExceptionType exceptionType, final Object... messageArguments) {
        LOG.error(MessageFormat.format(exceptionType.getMessage(), messageArguments));
        return new TerminologyServerException(exceptionType, messageArguments);
    }
}

Step 4 - At any place in your application, you can now throw an exception, and this will log the exception in the application logs. This exception is thrown and picked up by the DefaultExceptionHandler thanks to the Spring @ControllerAdvice annotation:

throw ExceptionFactory.create(ExceptionType.INTERNAL_SERVER_ERROR);

Like this you cope with the Exception handling process as a cross-cutting concern. No internal server errors will be propagated to the end user, and both expected and unexpected exceptions are handled by the DefaultExceptionHandler. The exception is assigned a certain HTTP error code and error message, which will be returned to the client.

like image 122
Mathias G. Avatar answered Oct 31 '22 13:10

Mathias G.


It is a good practice to have a dedicated class annotated with @ControllerAdvice which handles all unexpected problems. By doing this you prevent exposing the internals of your application to the client.

@ControllerAdvice
public class UncaughtExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(UncaughtExceptionHandler.class);

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public void handleAll(Exception e) {
        log.error("Unhandled exception occurred", e);
    }

}

For expected exceptions (not to confuse with checked exceptions) you probably what to handle the problem in place where it occurs. Some exceptions may be propagated or wrapped and rethrown to same global handler implemented as @ControllerAdvice to keep the whole logic dedicated for exceptions in a single spot.

like image 28
Daniel Olszewski Avatar answered Oct 31 '22 13:10

Daniel Olszewski