It took a couple of hours to figure this out - the mighty Google and even StackOverflow let me down - in the end I had to actually read Spring's DispatcherServlet code! (I know, right!?)

Here's the problem I was having - I'm using Spring MVC's data-binding tricks to inject objects into my @Controller's methods like this:

@Controller
@RequestMapping("things/{thing}.html")
class MyController {
    public ModelAndView thing(@PathVariable Thing aThing) {
        // Thing should be magically mapped from 
        // the {thing} part of the url

        return new ModelAndView(..blah..);
    }
}

I have global Formatter's configured as described in my previous post, and I want my method parameters to be automatically conjured from @PathVariable's and so on.

So far so good .. until I make a screw-up and parameter binding fails for any reason, at which point Spring's exception-handling kicks in. When that happens, Spring eats the exception and dumps me on the worlds shittiest error page saying:

HTTP ERROR 400
Problem accessing /your/url/whatever.html. Reason:
    Bad Request

Wow, thanks Spring!

To blame here are Spring's default set of HandlerExceptionResolver's, which are specified in DispatcherServlet.properties in the spring-webmvc jar. In 3.1.2 it says:

org.springframework.web.servlet.HandlerExceptionResolver=
    org.springfr..AnnotationMethodHandlerExceptionResolver,
    org.springfr..ResponseStatusExceptionResolver,
    org.springfr..DefaultHandlerExceptionResolver

(I've shortened the package-names to keep things readable)

Beats me why the default is to eat the exception without even logging it when Spring is normally so chatty about everything it does, but there you go. OK, so we need to configure some custom exception-handling so we can find out what's actually going wrong. There are two ways (that I know of) to do that:

  1. Use @ExceptionHandler annotated methods in our @Controller's to handle exceptions on a per-controller basis (or across more than one @Controller if you have a hierarchy and implement the @ExceptionHandler method high-up in the hierarchy).
  2. Register a HandlerExceptionResolver implementation to deal with exceptions globally (ie. across all @Controller's, regardless of hierarchy).

@ExceptionHandler

These bad-boys are straight-forward to use - just add a method in your @Controller and annotate it with @ExceptionHandler(SomeException.class) - something like this:

@Controller
class MyExceptionalController {
    @ExceptionHandler(Exception.class) 
    public void handleExceptions(Exception anExc) {
        anExc.printStackTrace(); // do something better than this ;)
    }

    @RequestMapping("/my/favourite/{thing}")
    public void showThing(@PathVariable Thing aThing) {
        throw new RuntimeException("boom");
    }
}

That exception-handler method will now be triggered for any exceptions that occur while processing this controller - including any exceptions that occur while trying to format the Thing parameter.

There's a bit more to it, for example you can parameterise the annotation with an array of exception-types. Shrug.

Just for completeness its worth mentioning that when formatting/conversion fails the exception presented to the @ExceptionHandler will be a TypeMismatchException, possibly wrapping a ConversionFailedException which in turn would wrap any exception thrown by your Formatter classes.

Custom HandlerExceptionResolver

This is the better approach, IMHO: Set up a HandlerExceptionResolver to deal with exceptions across all @Controller's and override with @ExceptionHandler's if you have specific cases that need special handling.

A deadly simple HandlerExceptionResolver might look like this:

package com.sjl.web;

import org.springframework.core.*;
import org.springframework.web.servlet.*

public class LoggingHandlerExceptionResolver 
implements HandlerExceptionResolver, Ordered {
    public int getOrder() {
        return Integer.MIN_VALUE; // we're first in line, yay!
    }

    public ModelAndView resolveException(
        HttpServletRequest aReq, HttpServletResponse aRes,
        Object aHandler, Exception anExc
    ) {
        anExc.printStackTrace(); // again, you can do better than this ;)
        return null; // trigger other HandlerExceptionResolver's
    }
}

Two things worth pointing out here:

  1. We are implementing Ordered and returning Integer.MIN_VALUE - this puts us at the front of the queue for resolving exceptions (and ahead of the default). If we don't implement Ordered we won't see the exception before one of the default handlers grabs and handles it. The default handlers appear to be registered with orders of Integer.MAX_VALUE, so any int below that will do.
  2. We are returning null from the resolveException method - doing this means that the other handlers in the chain get a chance to deal with the exception. Alternatively we can return a ModelAndView if we want to (and if we know how to deal with this particular kind of exception), which will prevent handlers further down the chain from seeing the exception.

There are some classes in Spring's HandlerExceptionResolver hierarchy that you might want to look at sub-classing - AbstractHandlerMethodExceptionResolver and SimpleMappingExceptionResolver are good ones to check first.

Of course we need to make Spring's DispatcherServlet aware of our custom HandlerExceptionResolver. The only configuration we need is:

<bean class="com.sjl.web.LoggingHandlerExceptionResolver"/>

No really, that's it.

There's an unusually high level of magic surrounding the DispatcherServlet, so although you must define your resolver as a bean in your spring config you do not need to inject it into any other spring beans. The DispatcherServlet will search for beans implementing the interface and automagically use them.

blog comments powered by Disqus