29

I have a Spring MVC application which uses FreeMarker as View technology (But maybe the view technology doesn't really matter for my question). I need to intercept all exceptions which may get thrown during a request.

I have implemented a HandlerExceptionResolver but this resolver is only executed when the exception occurs within a controller. But when a controller returns a ModelAndView and the exception occurs while rendering the view (Because a variable was not found or something like this) then the exception resolver is not called and instead I get a stack trace in the browser window.

I also tried using an exception handler method within the controller which returns the view and annotated it with @ExceptionHandler but this also doesn't work (Most likely again because the exception is not thrown in the controller but in the view).

So is there some Spring mechanism where I can register an exception handler which captures view errors?

kayahr
  • 20,913
  • 29
  • 99
  • 147
  • Would such [configuration](http://developingdeveloper.wordpress.com/2008/03/09/handling-exceptions-in-spring-mvc-part-2/) help? – nobeh Jun 28 '12 at 13:43
  • @nobeh Nope, unfortunately not. This article simply explains the usage of the HandlerExceptionResolver stuff. That's what I already use but it only captures exceptions thrown in controllers, not in views. – kayahr Jun 28 '12 at 13:57

3 Answers3

23

A word upfront: if you just need a "static" error page without much logic and model preparation, it should suffice to put a <error-page>-Tag in your web.xml (see below for an example).

Otherwise, there might be better ways to do this, but this works for us:

We use a servlet <filter> in the web.xml that catches all Exceptions and calls our custom ErrorHandler, the same we use inside the Spring HandlerExceptionResolver.

<filter>
   <filter-name>errorHandlerFilter</filter-name>
   <filter-class>org.example.filter.ErrorHandlerFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>errorHandlerFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

The implementation looks essentially like this:

public class ErrorHandlerFilter implements Filter {

  ErrorHandler errorHandler;

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
    try {
      filterChain.doFilter(request, response);
    } catch (Exception ex) {
      // call ErrorHandler and dispatch to error jsp
      String errorMessage = errorHandler.handle(request, response, ex);
      request.setAttribute("errorMessage", errorMessage);
      request.getRequestDispatcher("/WEB-INF/jsp/error/dispatch-error.jsp").forward(request, response);
    }

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    errorHandler = (ErrorHandler) WebApplicationContextUtils
      .getRequiredWebApplicationContext(filterConfig.getServletContext())
      .getBean("defaultErrorHandler");
  }

  // ...
}

I believe this should work pretty much the same for FreeMarker templates. Of course if your error view throws an error, you're more or less out of options.

To also catch errors like 404 and prepare the model for it, we use a filter that is mapped to the ERROR dispatcher:

<filter>
   <filter-name>errorDispatcherFilter</filter-name>
   <filter-class>org.example.filter.ErrorDispatcherFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>errorDispatcherFilter</filter-name>
  <url-pattern>/*</url-pattern>
  <dispatcher>ERROR</dispatcher>
</filter-mapping>

<error-page>
  <error-code>404</error-code>
  <location>/WEB-INF/jsp/error/dispatch-error.jsp</location>
</error-page>
<error-page>
  <exception-type>java.lang.Exception</exception-type>
  <location>/WEB-INF/jsp/error/dispatch-error.jsp</location>
</error-page>

The doFilter-Implementation looks like this:

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

  final HttpServletRequest request = (HttpServletRequest) servletRequest;

  // handle code(s)
  final int code = (Integer) request.getAttribute("javax.servlet.error.status_code");
  if (code == 404) {
    final String uri = (String) request.getAttribute("javax.servlet.error.request_uri");
    request.setAttribute("errorMessage", "The requested page '" + uri + "' could not be found.");
  }

  // notify chain
  filterChain.doFilter(servletRequest, servletResponse);
}
Archangel1C
  • 100
  • 11
oxc
  • 677
  • 3
  • 12
  • I have not used the custom error handler. All I want to do is - if template processing fails, then redirect user to a static errorpage. Browser shows the error page content but it also shows the stack trace. Any clues? I am using Spring. Just plain servlets with freemarker for views. – sbidwai Dec 13 '12 at 16:08
  • 1
    Hello, thank you for this, I'm getting `org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'defaultErrorHandler' is defined` on the init. Do I need to define this in a servlet.xml ? – Ernest Oct 15 '15 at 19:54
  • Is there a way to do this forward: `request.getRequestDispatcher("/WEB-INF/jsp/error/dispatch-error.jsp").forward(request, response);` using an existing ViewResolver? – FelipeKunzler Mar 03 '16 at 22:20
  • Hi oxc, thanks for your answer. I try your solution in my case, but I got the exception: java.lang.IllegalStateException: Cannot forward after response has been committed. Is there any way to work around ? – Zhli Jun 06 '18 at 11:06
  • @Zhil, there are other questions on Stackoverflow that will probably answer this question for you, perhaps https://stackoverflow.com/a/2125045/1479482 – oxc Jul 04 '18 at 09:09
7

You could extends the DispatcherServlet.

In your web.xml replace the generic DispatcherServlet for your own class.

<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>com.controller.generic.DispatcherServletHandler</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

Later create your own class DispatcherServletHandler and extends from DispatcherServlet:

public class DispatcherServletHandler extends DispatcherServlet {

    private static final String ERROR = "error";
    private static final String VIEW_ERROR_PAGE = "/WEB-INF/views/error/view-error.jsp";

    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        try{
            super.doService(request, response);
        } catch(Exception ex) {
            request.setAttribute(ERROR, ex);
            request.getRequestDispatcher(VIEW_ERROR_PAGE).forward(request, response);
        }
     }
}

And in that page we only have to show a message to the user.

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
cralfaro
  • 5,822
  • 3
  • 20
  • 30
0

Not sure if my solution works with the problem you're having. Ill just post the way i catch my exceptions to ensure no stack trace is show inside the browser:

I made an AbstractController class with a method that will handle a specific conflict like this:

public class AbstractController {

    @ResponseStatus(HttpStatus.CONFLICT)
    @ExceptionHandler({OptimisticLockingFailureException.class})
    @ResponseBody
    public void handleConflict() {
    //Do something extra if you want
    }
}

This way whenever an exception occurs the user will see a default HTTPResponse status. (eg. 404 Not Found etc..)

I extend this class on all my controller classes to make sure errors are redirected to the AbstractController. This way I don't need to use ExceptionHandler on a specific controller but I can add the globally to all my controllers. (by extending the AbstractController class).

Edit: After another go on your question, I noticed you're getting errors in your view. Not sure if this way will catch that error..

Hope this helps!!

Byron Voorbach
  • 4,365
  • 5
  • 27
  • 35
  • Already tried this. I added such a method directly in the controller and not in some base class of it, but this doesn't make a difference. This exception handler is called when the exception is thrown within the controller method, but not when the exception is thrown in the view. – kayahr Jun 28 '12 at 13:54
  • or if the error happens in a servlet filter. I think the accepted answer is the only one that handles any error anywhere. – ticktock Mar 10 '15 at 17:25
  • This won't work. The template processing happens AFTER controllers and controller advice are called. So the only way I can think of is to add handline to filter as suggested by accepted answer. Not pretty but makes sense. – Ravish Bhagdev Feb 24 '16 at 13:24