Jan
Jan

Reputation: 2833

Catch parameter parsing exception in Spring 3.0 WebMVC

I use Spring WebMVC to provide a REST API. I use methods like

@RequestMapping("/path({id}") void getById(@PathVariable("id") int id) {} methods.

When the client incorrectly put a string instead of an integer id into the query, I get a NumberFormatException like:

java.lang.NumberFormatException: For input string: "dojo"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
    at java.lang.Long.parseLong(Long.java:410)
    at java.lang.Long.valueOf(Long.java:525)
    at org.springframework.util.NumberUtils.parseNumber(NumberUtils.java:158)
    at org.springframework.core.convert.support.StringToNumberConverterFactory$StringToNumber.convert(StringToNumberConverterFactory.java:59)
    at org.springframework.core.convert.support.StringToNumberConverterFactory$StringToNumber.convert(StringToNumberConverterFactory.java:1)
    at org.springframework.core.convert.support.GenericConversionService$ConverterFactoryAdapter.convert(GenericConversionService.java:420)
    at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:37)
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:135)
    at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:199)
    at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:104)
    at org.springframework.beans.SimpleTypeConverter.convertIfNecessary(SimpleTypeConverter.java:47)
    at org.springframework.validation.DataBinder.convertIfNecessary(DataBinder.java:526)
    at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolvePathVariable(HandlerMethodInvoker.java:602)
    at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveHandlerArguments(HandlerMethodInvoker.java:289)
    at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:163)
    at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:414)
    at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:402)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:771)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:716)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:647)

My Question is now, how can I elegantly catch it? I know that Spring provides @ExeptionHandler annotations but I don't want to catch the NFE in general. I want to be able to catch all parsing exception in order to present a nice error message to the client.

Any ideas?

Cheers,

Jan

Upvotes: 8

Views: 4859

Answers (5)

Paweł Kaczorowski
Paweł Kaczorowski

Reputation: 1562

I have found solution for your problem here http://www.coderanch.com/t/625951/Spring/REST-request-mapping-parameter-type

Just try

@RequestMapping("/path({id:[\\d]+}") void getById(@PathVariable("id") int id) {} methods.

And then not valid usage will cause 404. I'm not sure if version 3.0 supports this.

Upvotes: 2

Jan
Jan

Reputation: 2833

putting your comments together, I tried the following:

public class ValidatingAnnotationMethodHandlerAdapter extends AnnotationMethodHandlerAdapter {

@Override
protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object target, String objectName) throws Exception {
    return new ServletRequestDataBinder(target, objectName) {

        @Override
        public <T> T convertIfNecessary(Object value, Class<T> requiredType) throws TypeMismatchException {
            try {
                return super.convertIfNecessary(value, requiredType);
            } catch (RuntimeException e) {
                throw new ControllerException("Could not parse parameter: " + e.getMessage());
            }
        }

        @Override
        public <T> T convertIfNecessary(Object value, Class<T> requiredType, MethodParameter methodParam) throws TypeMismatchException {
            try {
                return super.convertIfNecessary(value, requiredType, methodParam);
            } catch (RuntimeException e) {
                throw new ControllerException("Could not parse parameter: " + e.getMessage());
            }
        }

    };
}

ControllerException is a custom exception which is catched by an @ExceptionController annotated method (I use this exception in all validator classes).

Hope you like it,

Jan

Upvotes: 0

DwB
DwB

Reputation: 38348

Perhaps I do this because I am an old tyme programmer, but I use String as the type for all @PathVariable and @RequestParameter parameters then I do the parsing inside the handler method. This allows me to easily catch all NumberFormatException exceptions.

Although this is not the "Spring" way of doing this, I recommend it because it is easy for me and easy for my future offshore maintenance programmers to understand.

Upvotes: 0

Affe
Affe

Reputation: 47994

Is that the actual exception? (it doesn't match your code example) Normally one would expect that to be wrapped in org.springframework.beans.TypeMismatchException which is probably specific enough that you could write an @ExceptionHandler method for it.

If that's not specific enough, you will need to forgo the Spring-Magic and just change the parameter type to String + parse it yourself. Then you can handle it any way you like.

Upvotes: 2

Teja Kantamneni
Teja Kantamneni

Reputation: 17492

I am not 100% sure about whether this works for @PathVaribale or not, but generally for model binding you could use a BindingResult object next to your path variable and model and parsing error will be added to the BindingResult/Errors object.

Upvotes: 0

Related Questions