Spring Boot Exception Handler get all Exceptions at once - java

Is there a way to catch all exceptions at once that are thrown from a web request in a Spring Boot Exception Handler? I know I can catch an array of Exception types in my method annotated with #ExceptionHandler but it's not the types I'm talking about. I need something like a list of Exception objects. I already tried
##ExceptionHandler({ MethodArgumentTypeMismatchException.class, ConstraintViolationException.class })
#ResponseBody
private Error handleException(final List<Exception> ex, WebRequest request) {
...
}
but Spring is not able to find a suitable resolver for that:
java.lang.IllegalStateException: Could not resolve parameter [0] in private com.example.demo.model.Error com.example.demo.exception.MyExceptionHandler.handleException(java.util.List<java.lang.Exception>,org.springframework.web.context.request.WebRequest): No suitable resolver
With catching only one Throwable object it works fine:
#ExceptionHandler({ MethodArgumentTypeMismatchException.class, ConstraintViolationException.class })
#ResponseBody
private Error handleException(final Exception ex, WebRequest request) {
...
}
But what to do if I have different parameter violations like e.g. a ConstraintViolationException and a MethodArgumentTypeMismatchException in the same request?
If it's not possible to process a list of exceptions, how can I satisfy RFC-7807 (see https://www.rfc-editor.org/rfc/rfc7807)? Which means: How can I collect all invalid parameters, no matter what's the causing exception?

https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-exceptionhandler
#ExceptionHandler
public ResponseEntity<String> handle(Exception ex) {
// ...
}
you will catch most general exception. Then you can get suppressed
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Throwable.html#getSuppressed()
you cant throw more than one exception at once

I don't know about the annotation, but to catch multiple exceptions you do (remember that the first matching catch block will execute):
#ResponseBody
public MyError handleException(List<Throwable> exceptions, WebRequest request) {
try {
//...
} catch (ConstraintViolationException e) {
//...
} catch (MethodArgumentTypeMismatchException e) {
//...
}
}

Related

How to add an Exception Handler for an exception type that could be custom or default

I have a use case where for certain exception's that are normally thrown by a web framework that I can override the value by using my own MyCustomException object.
#ExceptionHandler({SomeWebFrameworkException.class})
public ResponseEntity<Object> handleException(MyCustomException exception) { ... }
However, if I want an exception handler to be able to be able to accept my custom exception then, I would need to cover all cases of this web framework error being thrown. Is there a way to somehow make it accept MyCustomException as input otherwise just default to a normal Exception ? If I just use a simple Exception as the input then, it would end up getting treated as the SomeWebFrameworkException instead of my own.
#ExceptionHandler({SomeWebFrameworkException.class})
public ResponseEntity<Object> handleException(Exception exception1, MyCustomException exception2) { ... }
You can have multiple exception handling methods defined, similarly to catch-blocks
#ExceptionHandler(MyCustomException.class)
public ResponseEntity<Object> handleMyCustomException(MyCustomException exception) { ... }
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleGenericException(Exception 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);
}
}

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...
}
}

How to catch all unhandled exceptions (i.e. without existing #ExceptionHandler) in Spring MVC?

I want to let HandlerExceptionResolver resolve any Exceptions that I don't explicit catch via #ExceptionHandler annotation.
Anyways, I want to apply specific logic on those exceptions. Eg send a mail notification or log additionally. I can achieve this by adding a #ExceptionHandler(Exception.class) catch as follows:
#RestControllerAdvice
public MyExceptionHandler {
#ExceptionHandler(IOException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public Object io(HttpServletRequest req, Exception e) {
return ...
}
#ExceptionHandler(Exception.class)
public Object exception(HttpServletRequest req, Exception e) {
MailService.send();
Logger.logInSpecificWay();
//TODO how to continue in the "normal" spring way with HandlerExceptionResolver?
}
}
Problem: if I add #ExceptionHandler(Exception.class) like that, I can catch those unhandled exceptions.
BUT I cannot let spring continue the normal workflow with HandlerExceptionResolver to create the response ModelAndView and set a HTTP STATUS code automatically.
Eg if someone tries a POST on a GET method, spring by default would return a 405 Method not allowed. But with an #ExceptionHandler(Exception.class) I would swallow this standard handling of spring...
So how can I keep the default HandlerExceptionResolver, but still apply my custom logic?
To provide a complete solution: it works just by extending ResponseEntityExceptionHandler, as that handles all the spring-mvc errors.
And the ones not handled can then be caught using #ExceptionHandler(Exception.class).
#RestControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> exception(Exception ex) {
MailService.send();
Logger.logInSpecificWay();
return ... custom exception
}
}
Well, I was facing the same problem some time back and have tried several ways like extending ResponseEntityExceptionHandler but all them were solving some problems but creating other ones.
Then I have decided to go with a custom solution which was also allowing me to send additional information and I have written below code
#RestControllerAdvice
public class MyExceptionHandler {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#ExceptionHandler(NumberFormatException.class)
public ResponseEntity<Object> handleNumberFormatException(NumberFormatException ex) {
return new ResponseEntity<>(getBody(BAD_REQUEST, ex, "Please enter a valid value"), new HttpHeaders(), BAD_REQUEST);
}
#ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Object> handleIllegalArgumentException(IllegalArgumentException ex) {
return new ResponseEntity<>(getBody(BAD_REQUEST, ex, ex.getMessage()), new HttpHeaders(), BAD_REQUEST);
}
#ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<Object> handleAccessDeniedException(AccessDeniedException ex) {
return new ResponseEntity<>(getBody(FORBIDDEN, ex, ex.getMessage()), new HttpHeaders(), FORBIDDEN);
}
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> exception(Exception ex) {
return new ResponseEntity<>(getBody(INTERNAL_SERVER_ERROR, ex, "Something Went Wrong"), new HttpHeaders(), INTERNAL_SERVER_ERROR);
}
public Map<String, Object> getBody(HttpStatus status, Exception ex, String message) {
log.error(message, ex);
Map<String, Object> body = new LinkedHashMap<>();
body.put("message", message);
body.put("timestamp", new Date());
body.put("status", status.value());
body.put("error", status.getReasonPhrase());
body.put("exception", ex.toString());
Throwable cause = ex.getCause();
if (cause != null) {
body.put("exceptionCause", ex.getCause().toString());
}
return body;
}
}
Create classes for exception handling in this way
#RestControllerAdvice
public class MyExceptionHandler extends BaseExceptionHandler {
}
public class BaseExceptionHandler extends ResponseEntityExceptionHandler {
}
Here ResponseEntityExceptionHandler is provided by spring and override the several exception handler methods provided by it related to the requestMethodNotSupported,missingPathVariable,noHandlerFound,typeMismatch,asyncRequestTimeouts ....... with your own exception messages or error response objects and status codes
and have a method with #ExceptionHandler(Exception.class) in MyExceptionHandler where the thrown exception comes finally if it doesn't have a matching handler.
I had the same issue and solved it creating a implementation of the interface HandlerExceptionResolver and removing the generic #ExceptionHandler(Exception.class) from the generic handler method.
.
It works this way:
Spring will try to handle the exception calling MyExceptionHandler first, but it will fail to find a handler because the annotation was removed from the generic handler. Next it will try other implementations of the interface HandlerExceptionResolver. It will enter this generic implementation that just delegates to the original generic error handler.
After that, I need to convert the ResponseEntity response to ModelAndView using MappingJackson2JsonView because this interface expects a ModelAndView as return type.
#Component
class GenericErrorHandler(
private val errorHandler: MyExceptionHandler,
private val objectMapper: ObjectMapper
) : HandlerExceptionResolver {
override fun resolveException(request: HttpServletRequest, response: HttpServletResponse, handler: Any, ex: Exception): ModelAndView? {
// handle exception
val responseEntity = errorHandler.handleUnexpectedException(ex)
// prepare JSON view
val jsonView = MappingJackson2JsonView(objectMapper)
jsonView.setExtractValueFromSingleKeyModel(true) // prevents creating the body key in the response json
// prepare ModelAndView
val mv = ModelAndView(jsonView, mapOf("body" to responseEntity.body))
mv.status = responseEntity.statusCode
mv.view = jsonView
return mv
}
}

Spring MVC #ExceptionHandler method in controller never being invoked

I have a Spring MVC controller with some simple REST service requests. I would like to add some error handling when specific exceptions are thrown from my services, but I cannot get a handler method annotated with #ExceptionHandler to actually ever be called. Here is one service I am deliberately throwing an exception to try and get my handler method to take over. The handler method is never invoked and Spring just returns a 500 error to the calling client. Do you have any ideas on what I'm doing wrong?
#ExceptionHandler(IOException.class)
public ModelAndView handleIOException(IOException ex, HttpServletRequest request, HttpServletResponse response) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
System.out.println("It worked!");
return new ModelAndView();
}
#RequestMapping(value = "/json/remove-service/{id}", method = RequestMethod.DELETE)
public void remove(#PathVariable("id") Long id) throws IOException {
throw new IOException("The handler should take over from here!");
}
Frustratingly, I have suffered from this as well. I discovered that if you mistakenly implement Throwable instead of Exception the Exception resolver will just rethrow your Throwable as a IllegalStateException. This will fail to invoke your #ExceptionHandler.
If you've implemented Throwable instead of Exception try changing it to Exception instead.
Here's the code in question from InvocableHandlerMethod
catch (InvocationTargetException e) {
// Unwrap for HandlerExceptionResolvers ...
Throwable targetException = e.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
else if (targetException instanceof Error) {
throw (Error) targetException;
}
else if (targetException instanceof Exception) {
throw (Exception) targetException;
}
else {
String msg = getInvocationErrorMessage("Failed to invoke controller method", args);
throw new IllegalStateException(msg, targetException);
}
}
This tip on the Spring forum may help you.
Likely you have configured the beans for your DispatchServlet in a webmvc-servlet.xml file (the *-servlet.xml file may be named differently)
If the XML file already includes another ExceptionResolver (like SimpleMappingExceptionResovler Spring wont automatically add any other resolvers for you. So manually adding the annotation resolver like so:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver" />
Should enable the #HandlerException processing.
I have found that #ExceptionHandler works with Throwable when you define method in such a manner:
#ExceptionHandler(Throwable.class)
#ResponseBody
public String handleException(Throwable e) {
}
In this case method has only one argument of type Throwable. If I try to use in this method some additional parameters (I tried to use Model), I receive 500 exception (this method isn't call). However this still works when additional parameters are HttpServlerRequest or HttpServlerResponse.
It will not work, because when you return String you return View name.
Now your Spring MVC controller is searching for This method is never called! Why not?! view and can find.
Make sure that #ExceptionHandler is mapped to existing view.

Categories

Resources