Call global #ControllerAdvice method from controller #ExceptionHandler method - java

Is it possible to handle exceptions in a controller using #ExceptionHandler and then rethrow that exception so a #ControllerAdvice can handle it?
I am trying to do it but when I rethrow the exception it doesn't reach the ControllerAdvice.
#RestController
public class MyController {
#GetMapping
public Something getSomething() {
...
}
#ExceptionHandler(Exception.class)
public void handleException(Exception e) throws Exception {
System.out.println("Log!");
throw e;
}
#RestControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<...> handleException(Exception exception) {
// Not working
...
}
Some more context (hopefully you can give me some different ideas on implementing this):
Today our application has a GlobalExceptionHandler (annotated with RestControllerAdvice) that handles exceptions.
Now, we have a new business demand that 2 particular endpoints we have must now log some additional info like "endpoint GET /something got this error".
Any kind of exception must be logged, I need to log which particular endpoint it happened and I need to keep the global handler in place as it is responsible for creating the error responses.
One way to solve this would be to change each method on the global handler to inject the current request, retrieve the url from it, check a particular configuration if I need to log for that endpoint and log it, but it feels wrong adding this behaviour to all handler methods.

Can you use the aspect of AOP and wrap your request with catch an any exceptions?
#Aspect
#Component
public class RequestWrapperAspect {
#Around("(#annotation(org.springframework.web.bind.annotation.RequestMapping) || " +
"#annotation(org.springframework.web.bind.annotation.PostMapping) || " +
"#annotation(org.springframework.web.bind.annotation.PutMapping) || " +
"#annotation(org.springframework.web.bind.annotation.DeleteMapping) || " +
"#annotation(org.springframework.web.bind.annotation.GetMapping) || " +
"#annotation(org.springframework.web.bind.annotation.PatchMapping)) && execution(public * *(..))")
public Object wrapRequest(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
try {
return proceedingJoinPoint.proceed();
} catch (Exception e) {
//whatever you need to do
throw e;
}
}

Related

Java Spring ExceptionHandler Controller syntax and good practices

I would like to handle my exceptions correctly in Spring, so I have a question about exceptionHandler syntax : Is it ok to throw specific exceptions in the controller, if they are caught by the exception handler ?
More specifically :
Here is the Exception :
public class UnknownUserException extends Exception {
private static final long serialVersionUID = 1L;
public UnknownUserException(String message) {
super(message);
}
}
Here is the ExceptionHandler with the specific method for UnknownUserException :
#ControllerAdvice
#ResponseBody
public class ControllerExceptionHandler {
#ExceptionHandler(UnknownUserException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public ErrorMessage unknownUserExceptionMessage(UnknownUserException ex, WebRequest request) {
ErrorMessage message = new ErrorMessage("The user doesn't exist: " +ex.getLocalizedMessage(), ex);
return message;
}
}
Here is one example of a mapping that may raise that exception :
#GetMapping({"/user/{id}"})
public ResponseEntity<UserProfileDto> getById(#PathVariable Long id) throws UnknownUserException {
UserProfileDto user = userService.findById(id);
return ResponseEntity.ok(user);
}
The userService.findById(id) may throw the UnknownUserException.
From what I understood, the controllerAdvice "overrides" the controller in case of that specific exception thrown by a service, but then, what should I do with my controller ? should I throw the exception again (like above) or catch the specific exception and return a ResponseEntity(HttpStatus.NOT_FOUND) ?
In an ideal scenario exception should be thrown immediately when it is known in your case as you mentioned service method will throw UnknownUserException that the right thing to do. Your Controller Advice should be able to handle that exception. ContollerAdvice will handle any matching exception that is thrown during execution of the request irrespective of the origin of exception.
Refer to this link for other options on handling exception

Custom exception handler for status NotFound

I want to implement custom exception handler for status NotFoundException for Spring Boot:
#ExceptionHandler({ AccessDeniedException.class, NotFoundException.class })
public ResponseEntity<ErrorResponseDTO> accessDeniedExceptionHandler(final AccessDeniedException ex) {
......
}
I can't find what is the proper import for NotFoundException Do you know what exception what is the proper import for that case?
Either add an exception handler for a NoHandlerFoundException:
#ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<ErrorResponseDto> handle(NoHandlerFoundException e) {
// ...
}
Or have your controller advice extend ResponseEntityExceptionHandler and override the handleNoHandlerFoundException method.
By the way, your code snippet declares a handler for two different exceptions while the method parameter final AccessDeniedException ex explicitly expects an exception of type AccessDeniedException. I would suggest either declaring multiple handler methods or generalize the paramater to an Exception instead.
I agree with #Michiel on, method parameter(AccessDeniedException ex) should be parent class of below classes:
AccessDeniedException
NotFoundException
try this
#ExceptionHandler({ AccessDeniedException.class, NotFoundException.class })
public ResponseEntity<ErrorResponseDTO> accessDeniedExceptionHandler(final **Exception** ex) {
......
}
i have used #ControllerAdvice like
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ExceptionHandler({BadRequestException.class, IllegalArgumentException.class, MaxUploadSizeExceededException.class})
#ResponseBody
public ResponseEntity<ErrorResponse> handleBadRequestException(Exception exception, WebRequest request) {
String message = StringUtils.isEmpty(exception.getMessage()) ? properties.getGeneralMessages().get("fail") : exception.getMessage();
if (message.contains(";"))
message = message.substring(0, message.indexOf(";"));
return getResponseEntity(message, null);
}
}

Spring MVCException handling

I have a MVC controller having at least 50 functions in there and my call to the services are not wrapped around try catch and some of the exceptions are getting eaten. I am trying to find the best way to handle this.
Shall I wrap the calls around try catch in individual function or is there any function I can use that can log the exception. I dont want to send alternate view or something just simply want to record in the logs.
You should probably look at #ExceptionHandler (controller-based exception Handling) or #ControllerAdvice (global exception handling). This article explains both in detail.
The other possible solution is AOP.
You could extend SimpleMappingExceptionResolver and override logException() like this:
public class CustomExceptionResolver extends SimpleMappingExceptionResolver {
#Override
protected void logException(Exception ex, HttpServletRequest request) {
if (ex != null) {
logger.error(ex);
}
}
}
However, if the exception occurs in the view, the above will not log it. For this, you can extend HandlerInterceptorAdapter:
public class ViewExceptionLoggerInterceptor extends HandlerInterceptorAdapter {
protected final Log logger = LogFactory.getLog(getClass());
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
if (ex != null) {
logger.error(ex);
}
}
}
To centralize exception handling, use the follow:
#ControllerAdvice
public class MyExceptionHandler{
#ExceptionHandler(SomeException.class)
public final ResponseEntity<ReturnValue> handleSomeException(SomeException ex) {
// exception handling
}
}
If you need ModelAndView as a return value, just change the return type to it, and build the proper object in the body.

Implementing the ResponseErrorHandler Interface

I'm trying to override the ResponseErrorHandler interface to be able to return the entire request (status code, body etc.) in case of any response other than 2xx.
I noticed that the Spring (RestTemplate) default returns an exception in case of a response other than 2xx. I do not want to return an exception, I just want to be able to return a:
new ResponseEntity(HttpStatus.STATUS_CODE)
Following some tutorials, I've found the following code:
#Component
public class LoginErrorHandler
implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse httpResponse)
throws IOException {
return (
httpResponse.getStatusCode().series() == CLIENT_ERROR
|| httpResponse.getStatusCode().series() == SERVER_ERROR);
}
#Override
public void handleError(ClientHttpResponse httpResponse)
throws IOException {
if (httpResponse.getStatusCode()
.series() == SERVER_ERROR) {
// handle SERVER_ERROR
} else if (httpResponse.getStatusCode()
.series() == CLIENT_ERROR) {
// handle CLIENT_ERROR
}
}
(Reference)
But I have not understood how I can return a ResponseEntity without changing the method return (which I can not by implementing the method).
Implementation:
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new LoginErrorHandler());
return restTemplate.postForEntity(url, request, String.class);
You can use Spring's ControllerAdvice and ExceptionHandler annotations to handle exceptions through your application. Below code returns 500 http status code if any exception encountered in your request. You can add other Exception classes or your own custom class to handle specific cases and return specific status codes to client.
Edit
Handling each code will not be a good idea. Rather you can wrap them in your custom exception and provide proper message to your client service. Still you can try something like below.
#Component
#ControllerAdvice
public class MyExceptionHandler {
#ExceptionHandler(HttpClientErrorException.BadRequest.class)
#ResponseStatus(code=HttpStatus.BAD_REQUEST, reason="Bad Request", value=HttpStatus.BAD_REQUEST)
public void handleBadRequest(HttpClientErrorException.BadRequest e) {
//handle bad request exception
}
#ExceptionHandler(HttpClientErrorException.NotFound.class)
#ResponseStatus(code=HttpStatus.NOT_FOUND, reason="Not Found", value=HttpStatus.NOT_FOUND)
public void handleNotFound(HttpClientErrorException.NotFound e) {
//handle Not Found
}
#ExceptionHandler(HttpServerErrorException.InternalServerError.class)
#ResponseStatus(code=HttpStatus.INTERNAL_SERVER_ERROR, reason="Internal Server Error", value=HttpStatus.INTERNAL_SERVER_ERROR)
public void handleInternalServerError(HttpServerErrorException.InternalServerError e) {
//handle internal server error
}
//more methods for each code.
}
Then handle the codes from in your rest template as below. Here you won't be able to return body of the response to the client.
#Component
public class LoginErrorHandler
implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse httpResponse)
throws IOException {
return (httpResponse.getStatusCode() != HttpStatus.OK);
}
#Override
public void handleError(ClientHttpResponse httpResponse)
throws IOException {
if (httpResponse.getRawStatusCode() >=400 && httpResponse.getRawStatusCode()<500 ) {
throw HttpClientErrorException.create(httpResponse.getStatusCode(), httpResponse.getStatusText(), httpResponse.getHeaders(), null, null);
}else if(httpResponse.getRawStatusCode() >=500){
throw HttpServerErrorException.create(httpResponse.getStatusCode(), httpResponse.getStatusText(), httpResponse.getHeaders(), null, null);
}else {
//throw some other exceptions for other codes and catch them in controller advice.
}
}
}
You can do this:
#Component
#ControllerAdvice
public class LoginErrorHandler{
#ExceptionHandler(HttpClientErrorException.class)
#ResponseBody
public void handleError(HttpClientErrorException e, HttpServletResponse response) throws IOException {
response.sendError(e.getRawStatusCode(), e.getStatusText());
}
}
This you generify all status code that should drop an exception, and it will return in the body.
There are two extensively used and very convenient exception handlers which are provided by Spring framework for centralized exception management in Spring Boot applications.
ResponseEntityExceptionHandler : exception thrown by our endpoint methods(methods annotated with #RequestMapping)
A convenient base class for #ControllerAdvice classes that wish to provide centralized exception handling across all #RequestMapping methods through #ExceptionHandler methods.
ResponseErrorHandler : Spring provides a hook ResponseErrorHandler which can be implemented to handle the exception thrown by external services. To call any external service most likely you will be using a RestTemplate. A RestTemplate can throw three types of exception as listed below :
HttpClientErrorException : For 4xx series status codes or Client errors.
HttpServerErrorException : For 5xx series status codes or server errors
RestClientException : Any other status codes like 3xx series
To simplify things we can handle these exceptions as a catch block separately but it lead to lot of boilerplate code scattered across our service. Strategy interface used by the RestTemplate to determine whether a particular response has an error or not.
There are two steps to using ResponseErrorHandler :
Step 1: Create a custom error handler class by implementing ResponseErrorHandler and implements its methods hasError and handleError
Step 2: We need to inject the ResponseErrorHandler implementation into the RestTemplate instance as follows :
By default in RestTemplate the errorHandler points to DefaultResponseErrorHandler.
Source

How to get all exception handlers annotated by #ExceptionHanlder?

How to get all exception handlers annotated by #ExceptionHanlder and I can call them manually?
Background
I need to handle some exceptions by my own exception handlers but in some situation my handled exceptions are not thrown directly by spring, and they are wrapped in the cause by. So I need to handle these caused by exceptions in one place using my own exception handling strategy in the existing #ExceptionHandlers. How can I do that?
Try to use Java Reflection Api to find classes annotated with "ExceptionHanlder". And invoke any method or whatever you want.
You can extend ResponseEntityExceptionHandler and make it a #ControllerAdvise like below.
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({YourException.class})
public ResponseEntity<Object> handleMyException(Exception ex, WebRequest request) {
... handle the way you like it
return new ResponseEntity<Object>(YourErrorObject, new HttpHeaders(), HttpStatus);
}
}
Spring provides #ControllerAdvice annotation that we can use with any class to define our global exception handler. The handler methods in Global Controller Advice is same as Controller based exception handler methods and used when controller class is not able to handle the exception.
You want to use exception handling strategy in your one place. that you can define multiple exception or make message using exception in exception controller.
like this :
#ExceptionHandler(value = { HttpClientErrorException.class, HTTPException.class, HttpMediaTypeException.class,
HttpMediaTypeNotSupportedException.class, HttpMessageNotReadableException.class })
or
#ExceptionHandler
#ResponseBody
ExceptionRepresentation handle(Exception exception) {
ExceptionRepresentation body = new ExceptionRepresentation(exception.getLocalizedMessage());
HttpStatus responseStatus = resolveAnnotatedResponseStatus(exception);
return new ResponseEntity<ExceptionRepresentation>(body, responseStatus);
}
HttpStatus resolveAnnotatedResponseStatus(Exception exception) {
ResponseStatus annotation = findMergedAnnotation(exception.getClass(), ResponseStatus.class);
if (annotation != null) {
return annotation.value();
}
return HttpStatus.INTERNAL_SERVER_ERROR;
}
Here is a work around. You can catch the the wrapping exception and then check the root cause of the exception. Here is an example of MySQLIntegrityConstraintViolationException which is wrapped by DataIntegrityViolationException in spring:
#ExceptionHandler(DataIntegrityViolationException.class)
#ResponseBody
public ResponseEntity<Object> proccessMySQLIntegrityConstraint(DataIntegrityViolationException exception) {
if (exception.getRootCause() instanceof MySQLIntegrityConstraintViolationException) {
doSomething....
} else {
doSomethingElse...
}
}

Categories

Resources