Different validation groups for method parameters and return value

Viewed 587

I would like to use different validation groups for a method's parameters and its return value. I am trying to Spring's @Validated annotation to achieve this. For example:

public interface UserOperations {

    @Validated({ResponseGroup.class})
    @Valid 
    User createUser(@Validated({RequestGroup.class}) @Valid User user);

}

It appears that return value is in fact getting validated against ResponseGroup, however the method argument user is getting validated against both ResponseGroup and RequestGroup. I see why this is happening when I look at: org.springframework.validation.beanvalidation.MethodValidationInterceptor#invoke

Is there an elegant way to apply one validation group to the method argument and different validation group to the return value?

1 Answers

The problem is that the method MethodValidationInterceptor.invoke looks for validation groups defined in an @Validated annotation on each method, or on the class, but not on individual parameters. The validation groups that are specified will be applied to the parameters as well as the return value.

In order to have different validation groups get applied to parameters and return values, you don't even need to specify groups inside the @Validated annotation; instead you can use @javax.validation.groups.ConvertGroup.

@Validated
public interface UserOperations {

    @NotNull
    @Valid
    @ConvertGroup(from = Default.class, to = User.Existing.class)
    User create(@NotNull @Valid @ConvertGroup(from = Default.class, to = User.New.class) User user);
}

public interface User {

    @Email(groups = {New.class, Existing.class})
    String getEmail();

    @Null(groups = {Existing.class})
    @NotNull(groups = {New.class})
    String getPassword();

    interface Existing {
    }

    interface New {
    }
}

Given the above, when UserOperations.create is called, the argument for user will get validated against the New group (must have a non-null password), while the return value will get validated against the Existing group (must have a null password).

As a side note, I realized Spring's method validation doesn't behave exactly in the same way as Spring MVC's automatic validation of @Valid @RequestBody. Additionally, If method validation is turned on for a Controller (has an @Validated annotation on the class), then the same @RequestBody method argument will get validated twice: once by standard method interceptor validation and once by MVC. To avoid MVC's validation on @RequestBody arguments of a given controller, I did the following:

@RestController
public class UserController implements UserOperations {
    ... 

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.setValidator(null);
    }

    ...
}
Related