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?
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.
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With