Difference Between @Valid and @Validated in Spring

Difference between @Valid and @Validated in Spring

As you quoted from the documentation, @Validated was added to support "validation groups", i.e. group of fields in the validated bean. This can be used in multi step forms where you may validate name, email, etc.. in first step and then other fields in following step(s).

The reason why this wasn't added into @Valid annotation is because that it is standardized using the java community process (JSR-303), which takes time and Spring developers wanted to allow people to use this functionality sooner.

Go to this jira ticket to see how the annotation came into existence.

Why is @Validated required for validating Spring controller request parameters?

Validation of objects is done by Hibernate Validator using the annotations from Jakarta Bean Validation 2.0. Something needs to trigger hibernate validator to run.

SpringMVC calls the controller methods when it sees a parameter with @Valid it will pass that object to hibernate validator. Hibernate validator will

  1. Examine the class of the object to figure out what validation rules have been put on the class fields
  2. Execute the validation rules against the fields marked up with validation annotations.

So in this case

@PutMapping("/{id}/other")
public void setOther(@PathVariable long id, @RequestBody @Valid MyFormObject form) {
/* ... */
}

MyFormObject has annotations on it that the hibernate validator can find to validate the object.

In this case

@PutMapping("/{id}/password")
public void setPassword(@PathVariable long id, @RequestBody @Size(min = 8) String password) {
/* ... */
}

java.lang.String does not have any annotations defined on it for hibernate validator to discover.

@Valid comes from the Bean validation package javax.validation.Valid while @Validated comes from Spring org.springframework.validation.annotation.Validated

@Validated annotation activates the Spring Validation AOP interceptor and it will examine method parameters to see if they have any validation annotations on them, if they do then Spring will call hibernate validator with each specific annotation for example @Size(min = 8) String password means call hibernate size validator and pass the value of the parameter password in this case hibernate validator does not need to scan java.lang.String to see if it has validation annotations on it. @Validated works on any spring @Component you can use it on @Service classes for example.

There is extra overhead for using @Validated similar to using @Transactional so that is why you have to opt into it. In the case of javax.validation.Valid Spring MVC needs to check the annotations on the controller method parameters so when it sees @Valid it is easy for it to send that object the Hibernate Validator without needing to add an AOP interceptor.

Relation between @Valid & @Validated for a @PathVariable validation using @Min

@Valid on a @ModelAttribute or @RequestBody is handled by the DispatcherServlet (or actually more accurate the Spring MVC RequestMappingHandlerAdapter which ultimately delegates this to the ModelAttributeMethodProcessor or RequestResponseBodyMethodProcessor which has this build in).

When adding plain javax.validation annotations to method arguments (regardless if this is a controller or not) you must annotate this class with @Validatied so that using AOP the MethodValidationInterceptor can be applied. They are a different mechanism for applying validation.

What does the @Valid annotation indicate in Spring?

It's for validation purposes.

Validation It is common to validate a
model after binding user input to it.
Spring 3 provides support for
declarative validation with JSR-303.
This support is enabled automatically
if a JSR-303 provider, such as
Hibernate Validator, is present on
your classpath. When enabled, you can
trigger validation simply by
annotating a Controller method
parameter with the @Valid annotation:
After binding incoming POST
parameters, the AppointmentForm will
be validated; in this case, to verify
the date field value is not null and
occurs in the future.


Look here for more info:

http://blog.springsource.com/2009/11/17/spring-3-type-conversion-and-validation/

Does @Valid work without @Validated on @RestController?

Yes @Valid will work without @Validated in @RestController.

In Spring, we use JSR-303's @Valid annotation for method level
validation. Moreover, we also use it to mark a member attribute for
validation. However, this annotation doesn't support group validation.
Groups help to limit the constraints applied during validation. One
particular use case is UI wizards. Here, in the first step, we may
have a certain sub-group of fields. In the subsequent step, there may
be another group belonging to the same bean. Hence we need to apply
constraints on these limited fields in each step, but @Valid doesn't
support this. In this case, for group-level, we have to use Spring's
@Validated, which is a variant of this JSR-303's @Valid. This is used
at the method-level. And for marking member attributes, we continue to
use the @Valid annotation.

You can read more about this in this link.

Use @Validated and @Valid with spring validator

Its pretty complicated thing to combine Spring validation and JSR-303 constrains. And there is no 'ready to use' way. The main inconvenience is that Spring validation uses BindingResult, and JSR-303 uses ConstraintValidatorContext as result of validation.

You can try to make your own validation engine, using Spring AOP. Let's consider, what we need to do for it. First of all, declare AOP dependencies (if you didn't yet):

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.8</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.8</version>
</dependency>

I'm using Spring of version 4.2.4.RELEASE, but of cause you can use your own. AspectJ needed for use aspect annotation. Next step, we have to create simple validator registry:

public class CustomValidatorRegistry {

private List<Validator> validatorList = new ArrayList<>();

public void addValidator(Validator validator){
validatorList.add(validator);
}

public List<Validator> getValidatorsForObject(Object o) {
List<Validator> result = new ArrayList<>();
for(Validator validator : validatorList){
if(validator.supports(o.getClass())){
result.add(validator);
}
}
return result;
}
}

As you see it is very simple class, which allow us to find validator for object. Now lets create annotation, that will be mark methods, that need to be validated:

package com.mydomain.validation;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomValidation {
}

Because of standard BindingException class is not RuntimeException, we can't use it in overriden methods. This means we need define our own exception:

public class CustomValidatorException extends RuntimeException {

private BindingResult bindingResult;

public CustomValidatorException(BindingResult bindingResult){
this.bindingResult = bindingResult;
}

public BindingResult getBindingResult() {
return bindingResult;
}
}

Now we are ready to create an aspect that will do most of the work. Aspect will execute before methods, which marked with CustomValidation annotation:

@Aspect
@Component
public class CustomValidatingAspect {

@Autowired
private CustomValidatorRegistry registry; //aspect will use our validator registry

@Before(value = "execution(public * *(..)) && annotation(com.mydomain.validation.CustomValidation)")
public void doBefore(JoinPoint point){
Annotation[][] paramAnnotations =
((MethodSignature)point.getSignature()).getMethod().getParameterAnnotations();
for(int i=0; i<paramAnnotations.length; i++){
for(Annotation annotation : paramAnnotations[i]){
//checking for standard org.springframework.validation.annotation.Validated
if(annotation.annotationType() == Validated.class){
Object arg = point.getArgs()[i];
if(arg==null) continue;
validate(arg);
}
}
}
}

private void validate(Object arg) {
List<Validator> validatorList = registry.getValidatorsForObject(arg);
for(Validator validator : validatorList){
BindingResult errors = new BeanPropertyBindingResult(arg, arg.getClass().getSimpleName());
validator.validate(arg, errors);
if(errors.hasErrors()){
throw new CustomValidatorException(errors);
}
}
}
}

execution(public * *(..)) && @annotation(com.springapp.mvc.validators.CustomValidation) means, that this aspect will applied to any public methods of beans, which marked with @CustomValidation annotation. Also note, that to mark validated parameters we are using standard org.springframework.validation.annotation.Validated annotation. But of cause we could make our custom. I think other code of aspect is very simple and does not need any comments. Further code of example validator:

public class PersonValidator implements Validator {
@Override
public boolean supports(Class<?> aClass) {
return aClass==Person.class;
}

@Override
public void validate(Object o, Errors errors) {
Person person = (Person)o;
if(person.getAge()<=0){
errors.rejectValue("age", "Age is too small");
}
}
}

Now we have make tune the configuration and all ready to use:

@Configuration
@ComponentScan(basePackages = "com.mydomain")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig{

.....

@Bean
public CustomValidatorRegistry validatorRegistry(){
CustomValidatorRegistry registry = new CustomValidatorRegistry();
registry.addValidator(new PersonValidator());
return registry;
}
}

Note, proxyTargetClass is true because we will use cglib class proxy.


Example of target method in service class:

@Service
public class PersonService{

@CustomValidation
public void savePerson(@Validated Person person){
....
}

}

Because of @CustomValidation annotation aspect will be applied, and because of @Validated annotation person will be validated. And example of usage of service in controller(or any other class):

@Controller
public class PersonConroller{

@Autowired
private PersonService service;

public String savePerson(@ModelAttribute Person person, ModelMap model){
try{
service.savePerson(person);
}catch(CustomValidatorException e){
model.addAttribute("errors", e.getBindingResult());
return "viewname";
}
return "viewname";
}

}

Keep in mind, that if you will invoke @CustomValidation from methods of PersonService class, validation will not work. Because it will invoke methods of original class, but not proxy. This means, that you can invoke this methods only from outside of class (from other classes), if you want validation to be working (eg @Transactional works same way).

Sorry for long post. My answer is not about 'simple declarative way', and possible you will do not need it. But I was curious resolve this problem.



Related Topics



Leave a reply



Submit