July 22, 2014

Tips & Tricks: Having fun with Spring validators

Today’s entry inaugurates our “tips & tricks” category. The tips or tricks that we will be discussing may not be marvels of engineering or anything profound in theoretical computing, however, they are very useful in practice and should be in the toolkit of every practitioner.

The topic for this post is the @Validated Spring annotation for the validation of parameters in the methods of a MVC controller. Since version 3.0 of the Spring framework, validation support was enhanced and improved including support for JSR-303 bean validation and correspondent annotations. We are not going to talk here about the JSR-303 bean validation per se. Instead, we will concentrate on a Spring variant of validation. Spring defines @Validated annotation as a variant of JSR-303 @Valid for:

“…supporting the specification of validation groups. Designed for convenient use with Spring’s JSR-303 support but not JSR-303 specific.”

When Spring MVC is configured to use this type of validation (an example can be found in the code attached) it is really easy to apply @Validated to the controllers’ method parameters to reap the benefits of automatic validation.

All you need to do is to define a validator, as follows:

    @Component
    public class IdValidator implements Validator {

        @Override
        public boolean supports(Class<?> clazz) {
            return String.class.equals(clazz);
        }

        @Override
        public void validate(Object target, Errors errors) {

            String id = (String) target;
            if(!StringUtils.isAlphanumeric(id)) {
                errors.reject("id.malformed");
            }
        }
    }

    @RequestMapping(value = "/path/{id}", method = RequestMethod.GET)
    public SampleDomainObject get(@Validated(IdValidator.class) @PathVariable String id) {

        return ... // obtain and return object
    }

…and then annotate your controller’s method parameter accordingly:

    @RequestMapping(value = "/path", method = RequestMethod.POST)
    public void create(@Validated @RequestBody SampleDomainObject domainObject) {
       // ... do work to create some record
    }

That’s really it, your validator will be called before any work is done in the method’s body and if validation fails the caller will get a response with code 400 – i.e. bad request.

Now, if you want you can change the response in the exception handler method of the controller as shown below:

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public void onValidationException(MethodArgumentNotValidException e, HttpServletResponse response) throws IOException {
      String errorMessages = ... // retrieve error messages from e.getBindingResult()
      response.sendError(HttpServletResponse.SC_BAD_REQUEST, errorMessages);
    }

If you like this new feature, you may want to apply this to any controller’s method parameters. You could, for example, write something like the following:

    @Component
    public class IdValidator implements Validator {
      @Override
      public boolean supports(Class<?> clazz) {
        return String.class.equals(clazz);
      }
      @Override
      public void validate(Object target, Errors errors) {
         String id = (String) target;
         if(!StringUtils.isAlphanumeric(id)) {
            errors.reject("id.malformed");
         }
      }
    }

    @RequestMapping(value = "/path/{id}", method = RequestMethod.GET)
    public SampleDomainObject get(@Validated(IdValidator.class) @PathVariable String id) {
      return ... // obtain and return object
    }

Unfortunately, if you were to run the above mentioned code, you would quickly realize that validation is not happening. So what is going on here?

As it turns out every parameter in the controller’s mapped method is handled by a separate argument resolver implementation, one for @PathVariable annotated parameter, another for @RequestParam annotated and so on.

Every such resolver has its own logic of creating and initializing the DataBinder that is behind all/part of the magic related to validation. It appears that Spring (at least 3.x) implements logic that involves execution of validators only for the argument resolvers that apply to the method parameters annotated with @RequestBody.

So, is this it? Does the implementation of parameter validation with @Validated fall short of its promise? Actually, no. Do not despair!

It turns out that there is an annotation (@ModelAttribute) that is sometimes used to build an object from the parameters in the URI.

Let’s try something like this:

    @RequestMapping(value = "/path/{id}/filed/{field}", method = RequestMethod.GET)
    public SampleDomainObject get(@ModelAttribute("pair") Pair pair) {
       return ... // obtain object using Pair and return it
    }

    @ModelAttribute("pair")
    private Pair createPair(@PathVariable String id, @PathVariable String field) {
      return new Pair(id, field);
    }

The use of the @ModelAttribute on a controller’s method parameter invokes a chain of argument resolvers.

The first resolver that will be called invokes the method createPair, which in turn invokes the argument resolvers for the path variables. The last resolver that is invoked is the ModelAttributeMethodProcessor.

It also so happens that the ModelAttributeMethodProcessor has the necessary logic to create and initialize a DataBinder, in such a way that performs any validations, if present.

So, if the above method is written as follows:

 
    @RequestMapping(value = "/path/{id}/filed/{field}", method = RequestMethod.GET)
    public SampleDomainObject get(@Validated @ModelAttribute("pair") Pair pair) {
     return ... // obtain object using Pair and return it
    }

…and if there is a validator that supports the Pair class then such validator will be executed.

By now you are probably wondering how is this relevant to the fact that we cannot use @Validated on the method parameters other than ones annotated with @RequestBody and, as we discovered, @ModelAttribute?

Well, here comes the trick that saves the day. If you annotate any controller’s method parameter with @ModelAttribute it forces Spring to apply ModelAttributeMethodProcessor to the parameter and, hence, validation will be performed, if validation is applicable.

The only thing you need is to use the following construct:

    @RequestMapping(value = "/path/{id}", method = RequestMethod.GET)
    public SampleDomainObject get(@ModelAttribute("id") @Validated(IdValidator.class) String id) {
      return ... // obtain and return object
    }

    @ModelAttribute("id")
    private String idAsModelAttribute(@PathVariable String id) {
      return id;
    }

Now, the Spring framework will happily validate your ID because it was tricked into thinking that it is a model attribute object and, hence, it will apply the IdValidator, as specified, to it. So, that’s great, right? We get our validation, for free!

Well, almost for free. We get the convenience of parameter validation with a bit of extra code. Moreover, the exception that needs to be handled, in case of any validation errors on a parameter annotated with @ModelAttribute, is BindException instead of MethodArgumentNotValidException – and no, they have no common parent class, or interface, in case you are wondering, so you would need to add another handler. Nevertheless, the above extra code seems to be a small price to pay for the convenience of validating method parameters.

Peter Litvak

Software Architect

Source Code: http://engineering-scratch-copyright-com.s3.amazonaws.com/babis/spring_validated.zip

Credits: Anton Mokshyn – amokshyn (at) copyright (dot) com. Anton provided the code of composite validator.