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;
}
Related
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.
I am working in a spring mvc based project and have developed a ExceptionResolver by extending DefaultHandlerExceptionResolver to redirect to error page depending on the exception type. It is working for exception raised at facade, service, DAO layer.
But it doesn't work for any exceptions raised in Servlet filter. What changes should be made for that?
Below is my handlerExceptionResolver
public ModelAndView doResolveException(final HttpServletRequest request, final HttpServletResponse response, final Object obj,
final Exception exception){
ModelAndView modelAndView = super.doResolveException(request, response, obj, exception);
modelAndView = Objects.nonNull(modelAndView) ? modelAndView : new ModelAndView();
final String url = Config.getParameter(EXCEPTION_HANDLER_URL);
modelAndView.setViewName(url);
final FlashMap outputFlashMap = RequestContextUtils.getOutputFlashMap(request);
outputFlashMap.put(ERROR_DETAILS, exception);
if (exception instanceof BusinessExecutionException)
{
return handleBusinessExecutionExceptionMethod((BusinessExecutionException) exception, outputFlashMap, modelAndView);
}
else if (exception instanceof IntegrationExecutionException)
{
return handleIntegrationExecutionExceptionMethod((IntegrationExecutionException) exception, outputFlashMap,
modelAndView);
}
else if (exception instanceof DataAccessObjectExecutionException)
{
return handleDAOExecutionExceptionMethod((DataAccessObjectExecutionException) exception, outputFlashMap, modelAndView);
}
return handleMiscException(exception, outputFlashMap, modelAndView);
}
Use Spring exception handler:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
It is convenient - you can "catch" exception regarding type and HTTP status.
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);
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;
}
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.