Reputation: 2258
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?
Upvotes: 7
Views: 12077
Reputation: 1717
I think best practice to handle exception is to use @ControllerAdvice i.e global exception handler.
Don't throw an exception from controller layer, throw exception from service layer and internally will call Global exception handler i.e ControllerAdvice class to execute corresponding exception method and then send proper error message to the response.
ControllerAdvice class
@ControllerAdvice
public class ExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> notFound(UserNotFoundException userNotFoundException) {
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.NOT_FOUND, userNotFoundException.getMessage());
return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.NOT_FOUND);
}
}
Controller code,
@GetMapping
public ResponseEntity<UserDto> getUser(@RequestParam("id") String id) {
UserDto userDto = userService.getUserById(id);
return new ResponseEntity<UserDto>(userDto, HttpStatus.OK);
}
Service layer code,
@Override
public User getUserById(String id) throws NotFoundException {
Optional<User> user = userRepository.findById(Integer.parseInt(id));
if (!user.isPresent()) throw new UserNotFoundException("Record Not Found!");
return user.get();
}
Upvotes: 0
Reputation: 185
I think the accepted answer is nice. It uses Spring @ControllerAdvice
for global exception handling, also the enum to combine the error code and HTTP status.
However, I disagree with the Exception factory part. The factory logs the exception and throws it out. This is not recommended in many articles like this and this. Of course, if you have no any exception translation like, it should be ok since the exception will be logged only once. But you just simply could not sure that the translation won't be needed in the future.
I would recommend do the logging with the @ControllerAdvice
since this is the final destination for your application exception. This could make sure that every exception will be log once and had a good handle to present to the client.
Upvotes: 1
Reputation: 21
There are three ways in which you can handle the exception using Spring Framework,
@ExceptionHandler - Controller Based
This handler is controller based, we need to have a method annotated with @ExceptionHandler which takes Exception Class[any exception which you want to handle] as argument, if any of these exception is raised in the controller, then this handler method will handle.
If we have two handler method in same controller say for example one handler for Exception and another handler for RuntimeException, then the handler method which is closer to Exception Class hirarchy is triggered. In this case, NullpointerException is thrown then IOException handler method is triggered, which is the closest to the Exception class.
@ControllerAdvice - Global Exception Handler
This is used for global error handling in Spring application. All you need to have is a class annotated with @ControllerAdvice. If any exception is raised in the defined controller [you can define to which packages this controller advice should listen for exception in base packages] then it is handled by ControllerAdvice.
You will have multiple @ExceptionHandler in ControllerAdvice as shown in the below snippet. This class can be single place where you can handle exception for the whole application.
@ControllerAdvice(basePackages = "{com.exampe.controller}")
public class RestApiExceptionHandlerAdvice {
/** Handling Business exception */
@ExceptionHandler(value = BadRequestException.class)
public ErrorMessage handleBadRequest(BadRequestException exception) {
//code...
return errMsg;
}
@ExceptionHandler(value = GatewayTimeoutException.class)
public ErrorMessage handleGatewayTimeout(GatewayTimeoutException exception) {
//code...
return errMsg;
}
}
HandlerExceptionResolver
By the above two methods, most of the times we use static pages. In this case, we can return different views to different exceptions. we can configure by using MySimpleMappingExceptionResolver in spring.xml and you can specify what view needs to be rendered for which exception.
Upvotes: 2
Reputation: 12542
You can use @ControllerAdvice
as a global exception handler in your spring application. Checkout this well explained tutorial. Using @ControllerAdvice
makes your business code less cluttered and then you have a seperate place where you can handle all your exceptions. @ControllerAdvice
makes your adhere to the design principle seperation of concerns.
Upvotes: 2
Reputation: 5085
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.
Upvotes: 9
Reputation: 14401
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.
Upvotes: 3
Reputation: 12342
You should try @ExceptionHandler
annotation.
You can read more about it here: https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
Upvotes: 2