Spring @Validated in service layer

Hej,

I want to use the @Validated(group=Foo.class) annotation to validate an argument before executing a method like following:

public void doFoo(Foo @Validated(groups=Foo.class) foo){}

When i put this method in the Controller of my Spring application, the @Validated is executed and throws an error when the Foo object is not valid. However if I put the same thing in a method in the Service layer of my application, the validation is not executed and the method just runs even when the Foo object isn't valid.

Can't you use the @Validated annotation in the service layer ? Or do I have to do configure something extra to make it work ?

Update:

I have added the following two beans to my service.xml:

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

and replaced the @Validate with @Null like so:

public void doFoo(Foo @Null(groups=Foo.class) foo){}

I know it is a pretty silly annotation to do but I wanted to check that if I call the method now and passing null it would throw an violation exception which it does. So why does it execute the @Null annotation and not the @Validate annotation ? I know one is from javax.validation and the other is from Spring but I do not think that has anything to do with it ?


Solution 1:

In the eyes of a Spring MVC stack, there is no such thing as a service layer. The reason it works for @Controller class handler methods is that Spring uses a special HandlerMethodArgumentResolver called ModelAttributeMethodProcessor which performs validation before resolving the argument to use in your handler method.

The service layer, as we call it, is just a plain bean with no additional behavior added to it from the MVC (DispatcherServlet) stack. As such you cannot expect any validation from Spring. You need to roll your own, probably with AOP.


With MethodValidationPostProcessor, take a look at the javadoc

Applicable methods have JSR-303 constraint annotations on their parameters and/or on their return value (in the latter case specified at the method level, typically as inline annotation).

Validation groups can be specified through Spring's Validated annotation at the type level of the containing target class, applying to all public service methods of that class. By default, JSR-303 will validate against its default group only.

The @Validated annotation is only used to specify a validation group, it doesn't itself force any validation. You need to use one of the javax.validation annotations like @Null or @Valid. Remember that you can use as many annotations as you would like on a method parameter.

Solution 2:

As a side note on Spring Validation for methods:

Since Spring uses interceptors in its approach, the validation itself is only performed when you're talking to a Bean's method:

When talking to an instance of this bean through the Spring or JSR-303 Validator interfaces, you'll be talking to the default Validator of the underlying ValidatorFactory. This is very convenient in that you don't have to perform yet another call on the factory, assuming that you will almost always use the default Validator anyway.

This is important because if you're trying to implement a validation in such a way for method calls within the class, it won't work. E.g.:

@Autowired
WannaValidate service;
//...
service.callMeOutside(new Form);

@Service
public class WannaValidate {

    /* Spring Validation will work fine when executed from outside, as above */
    @Validated
    public void callMeOutside(@Valid Form form) {
         AnotherForm anotherForm = new AnotherForm(form);
         callMeInside(anotherForm);
    }

    /* Spring Validation won't work for AnotherForm if executed from inner method */
    @Validated
    public void callMeInside(@Valid AnotherForm form) {
         // stuff
    }        
}

Hope someone finds this helpful. Tested with Spring 4.3, so things might be different for other versions.

Solution 3:

@pgiecek You don't need to create a new Annotation. You can use:

@Validated
public class MyClass {

    @Validated({Group1.class})
    public myMethod1(@Valid Foo foo) { ... }

    @Validated({Group2.class})
    public myMethod2(@Valid Foo foo) { ... }

    ...
}