Throwing an exception from #ExceptionHandler to get caught by another handler - java

I have a #ControllerAdvice class to handle exceptions from my SpringMVC controllers. I would like to catch an exception of a known type (RuntimeException) in an #ExceptionHandler method then throw the e.getCause() exception and have this exception caught by the same #ControllerAdvice class.
Sample code:
#ControllerAdvice
public class ExceptionHandlingAdvice
{
#ExceptionHandler( RuntimeException.class )
private void handleRuntimeException( final RuntimeException e, final HttpServletResponse response ) throws Throwable
{
throw e.getCause(); // Can be of many types
}
// I want any Exception1 exception thrown by the above handler to be caught in this handler
#ExceptionHandler( Exception1.class )
private void handleAnException( final Exception1 e, final HttpServletResponse response ) throws Throwable
{
// handle exception
}
}
Is this possible?

You can check if that RuntimeException is instance of Exception1.class and call the method directly:
private void handleRuntimeException( final RuntimeException e, final HttpServletResponse response ) throws Throwable
{
if (e instanceof Exception1) handleAnException(e,response);
else throw e.getCause(); // Can be of many types
}

Few years late on this.. but just ran into a need for this in dealing with #Async services - when throwing an exception, they get wrapped in the ExecutionException.class and wanted my controller advice to direct them to their proper handler, an identical situation you were in.
Using reflection, can gather all the methods on the controller advice, sift for the matching #ExceptionHandler annotation for e.getCause().getClass() then invoke the first found method.
#ControllerAdvice
public class ExceptionHandlingAdvice
{
#ExceptionHandler( RuntimeException.class )
private void handleRuntimeException( final RuntimeException e, final HttpServletResponse response )
{
if (e.getCause() != null) {
Optional<Method> method = Arrays.stream(Rest.Advice.class.getMethods())
.filter(m -> {
// Get annotation
ExceptionHandler annotation = m.getAnnotation(ExceptionHandler.class);
// Annotation exists on method and contains cause class
return annotation != null && Arrays.asList(annotation.value()).contains(e.getCause().getClass());
})
.findFirst();
if (method.isPresent()) {
try {
method.get().invoke(this, e.getCause(), response);
} catch (IllegalAccessException | InvocationTargetException ex) {
// Heard you like exceptions on your exceptions while excepting
ex.printStackTrace();
}
}
}
// Handle if not sent to another
}
... other handlers
}
Didn't test with void -- Personally, I return ResponseEntity<MyStandardErrorResponse> from my handlers, so my invoke line looks like:
return (ResponseEntity<MyStandardErrorResponse>) method.get().invoke(this, e.getCause(), request);

Related

Call global #ControllerAdvice method from controller #ExceptionHandler method

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

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);
}
}

Handling an exception thrown from ExceptionHandler in a filter

My logic was to implement a global exception filter which handles any exception inside my Spring MVC, and also has #ControllerAdvice to Handle exception
Global Filter
#Component
public class GlobalExceptionHandlerFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} catch (Throwable ex) {
...
}
}
}
Controller Advice
#ControllerAdvice
public class BaseController {
#ExceptionHandler(value = {ConstraintViolationException.class})
public void handlePersistenceException(ConstraintViolationException ex, HttpServletRequest request) throws MyException {
String str = "";
for (ConstraintViolation constraintViolation : ex.getConstraintViolations()) {
str += "Property '" + constraintViolation.getPropertyPath() + "' - " + constraintViolation.getMessage();
}
MyException myException = new MyException(str);
throw myException;
}
}
The globalfilter wraps the execution of the exception so it captures the ConstraintViolation.
But my logic needs to modify the ConstraintViolation as MyException and throw it from ControllerAdvice. The global still captures the ConstraintViolation, not the MyException which is thrown from ControllerAdvice, but the exception is not carried over to the filter when I am not throwing MyException.
How to override the ConstraintViolationException with MyException in ControllerAdvice to make it captured by the globalexception filter.
Wrap your MyException class in ResponseEntity and return it with the required HttpStatus.
#ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<MyException> notFoundException(HttpServletRequest request,
HttpMessageNotReadableException e) {
MyException myException = new MyException("CustomMessage");
logger.error("An constrain voilation occured reason {}", e);
return new ResponseEntity<>(myException , HttpStatus.BAD_REQUEST);
}
You cannot achieve the above required scenario, as the ExceptionHandler invoker(ExceptionHandlerExceptionResolver.doResolveHandlerMethodException) which invokes the method binded with the perfect match for the exception catches any exception from the invoked method.
The default action in this chain incase of exception in handler is to rethrow the original Exception which invoked the handler.

Why #ExceptionHandler(value = Throwable.class) doesn't catch OutOfMemoryError? [duplicate]

Ours is a Spring MVC based REST application. I am trying to use ExceptionHandler annotation to handle all errors and exceptions.
I have
#ExceptionHandler(Throwable.class)
public #ResponseBody String handleErrors() {
return "error";
}
This works whenever there is an exception thrown and it doesn't work for any errors.
I am using Spring 4.0. Is there any work-around?
Contrary to what the ExceptionHandler#value() attribute indicates
Class<? extends Throwable>[] value() default {};
and #ExceptionHandler is only meant to handle Exception and its sub types.
Spring uses ExceptionHandlerExceptionResolver to resolve your annotated handlers, using the following method
doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod, Exception exception)
which as you can see only accepts an Exception.
You cannot handle Throwable or Error types with #ExceptionHandler with this configuration.
I would tell you to provide your own HandlerExceptionResolver implementation which does handle Throwable instances, but you'd need to provide your own DispatcherServlet (and most of the MVC stack) yourself since DispatcherServlet does not catch Throwable instances at any place where you could make any significant difference.
Update:
Since 4.3, Spring MVC wraps a thrown Throwable value in a NestedServletException instance and exposes that to the ExceptionHandlerExceptionResolver.
You can do a kind of Hacking to capture Error in Spring MVC.
First, define an Interceptor like this :
public class ErrorHandlingInterceptor extends HandlerInterceptorAdapter {
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception
{
super.afterCompletion(request, response, handler, ex);
controller.handleError(ex.getCause(), request, response);
} }
Second, define a method in your controller like "handleError" method:
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setExceptionId(exceptionId);
errorResponse.setErrorMsg(ex.toString());
errorResponse.setServerStackTrace(serverStackTrace(ex));
response.setStatus(responseCode);
response.setContentType("application/json");
ObjectWriter writer = mapper.writer().withDefaultPrettyPrinter();
writer.writeValue(response.getOutputStream(), errorResponse);
Finally, config your interceptor in Spring configuration.
<mvc:interceptors>
<bean class="ErrorHandlingInterceptor" />
</mvc:interceptors>
Code in DispatchServlet:
catch (Exception ex) {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// This is where to handle Exception by Spring.
// If Error happens, it will go to catch Error statement
// which will call afterCompletion method
mv = processHandlerException(processedRequest, response, handler, ex);
errorView = (mv != null);
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
// Trigger after-completion for successful outcome.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
}
catch (Exception ex) {
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
}
catch (Error err) {
ServletException ex = new NestedServletException("Handler processing failed", err);
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
}

ExceptionHandler doesn't work with Throwable

Ours is a Spring MVC based REST application. I am trying to use ExceptionHandler annotation to handle all errors and exceptions.
I have
#ExceptionHandler(Throwable.class)
public #ResponseBody String handleErrors() {
return "error";
}
This works whenever there is an exception thrown and it doesn't work for any errors.
I am using Spring 4.0. Is there any work-around?
Contrary to what the ExceptionHandler#value() attribute indicates
Class<? extends Throwable>[] value() default {};
and #ExceptionHandler is only meant to handle Exception and its sub types.
Spring uses ExceptionHandlerExceptionResolver to resolve your annotated handlers, using the following method
doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod, Exception exception)
which as you can see only accepts an Exception.
You cannot handle Throwable or Error types with #ExceptionHandler with this configuration.
I would tell you to provide your own HandlerExceptionResolver implementation which does handle Throwable instances, but you'd need to provide your own DispatcherServlet (and most of the MVC stack) yourself since DispatcherServlet does not catch Throwable instances at any place where you could make any significant difference.
Update:
Since 4.3, Spring MVC wraps a thrown Throwable value in a NestedServletException instance and exposes that to the ExceptionHandlerExceptionResolver.
You can do a kind of Hacking to capture Error in Spring MVC.
First, define an Interceptor like this :
public class ErrorHandlingInterceptor extends HandlerInterceptorAdapter {
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception
{
super.afterCompletion(request, response, handler, ex);
controller.handleError(ex.getCause(), request, response);
} }
Second, define a method in your controller like "handleError" method:
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setExceptionId(exceptionId);
errorResponse.setErrorMsg(ex.toString());
errorResponse.setServerStackTrace(serverStackTrace(ex));
response.setStatus(responseCode);
response.setContentType("application/json");
ObjectWriter writer = mapper.writer().withDefaultPrettyPrinter();
writer.writeValue(response.getOutputStream(), errorResponse);
Finally, config your interceptor in Spring configuration.
<mvc:interceptors>
<bean class="ErrorHandlingInterceptor" />
</mvc:interceptors>
Code in DispatchServlet:
catch (Exception ex) {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// This is where to handle Exception by Spring.
// If Error happens, it will go to catch Error statement
// which will call afterCompletion method
mv = processHandlerException(processedRequest, response, handler, ex);
errorView = (mv != null);
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
// Trigger after-completion for successful outcome.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
}
catch (Exception ex) {
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
}
catch (Error err) {
ServletException ex = new NestedServletException("Handler processing failed", err);
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
}

Categories

Resources