Spring validation with @Valid

Validation features in Spring are universal. You can use annotation or make your own validator and bind it into the request. This article will focus on the first solution.

Its first part will present the annotation validation flow. In the second one, the components will be presented. The last part will contain the explanation for common error made by Spring beginner developers: the necessity of placing BindingResult directly after validated object.

Validation flow in Spring with @Valid annotation

To understand validation process with both standard Java @Valid or specific Spring @Validated annotations, we need first to understand how Spring resolves objects annotated with @ModelAttribute. They are annotated at the level of controller's methods signatures. @ModelAttribute annotation serves to "translate" dynamically request parameters into Java's object specified in this annotation. For example, this code @ModelAttribute("article") Article article will try to match all request parameters into the fields of Article's class. Now, suppose that this class has two fields: title and content. If the request contains title and content parameters, they will be used as Article's title and content values. The detailed way of resolving that is described in the article about ModelAttribute annotation in Spring.

When we already have the object to validate, a processor for @ModelAttribute annotation (org.springframework.web.method.annotation.ModelAttributeMethodProcessor) checks if validation annotation must be applied. Annotation validation must begin by "Valid" world. Next, the object is validated through public void validate(Object... validationHints) from org.springframework.validation.DataBinder class. This method iterates over all available validators and invoke validate method of every validator. The validator is taken from bean with "validator" id. Next, it can be associated into annotation-driven configuration entry:

<mvc:annotation-driven validator="validator" >

If no validator bean is specified, the default validator is taken: org.springframework.validation.beanvalidation.LocalValidatorFactoryBean.

How does validation is processed in Spring ?

We've already discovered the flow of validation. Now, we can focus on validation process itself, i.e. the how validator knows that one field is incorrect. LocalValidatorFactoryBean extends another validation class from the same package, SpringValidatorAdapter, but doesn't override its validate() methods. These methods are used to check if validates fields are correct or not. More precisely, SpringValidatorAdapter contains a target validator field (targetValidator of Validator type). It will be used inside validate() method to validate all fields of validated object.

The result of this validation is after processed by protected void processConstraintViolations(Set> violations, Errors errors) method in SpringValidatorAdapter. It appends errors from JSR-303 validator to given Spring's Errors object.

The validation errors are appended directly to DataBinder's private AbstractPropertyBindingResult bindingResult field. Its value is retrieved in ModelAttributeMethodProcessor, at this moment:

if (binder.getBindingResult().hasErrors()) {
	if (isBindExceptionRequired(binder, parameter)) {
		throw new BindException(binder.getBindingResult());
	}
}

Retreive BindingResult inside controller method

Note that to retrieve the BindingResult inside a controller's method, you must place BindingResult instance directly after validated object. It means that in this signature public String addArticle(@ModelAttribute("article") @Valid Article article, BindingResult result), BindingResult's instance will contain all validation errors. Now, if between Article and BindingResult instances you'll place another object (for example: HttpServletRequest request), an exception of this type will be thrown:

An Errors/BindingResult argument is expected to be declared immediately after the  model attribute, the @RequestBody or the @RequestPart arguments to which they apply. 

The content of this error message can be found at org.springframework.web.method.annotation.ErrorsMethodArgumentResolver class. This class is used to resolve Errors instance from method signatures. You can ask why ErrorsMethodArgumentResolver is used to resolve BindingResults ? Simply, it's caused by the fact that BindingResult interface extends the Errors interface. So, both can be resolved with the same argument resolver.

The method that can fail the validation process because of bad placement of BindingResult is short:

ModelMap model = mavContainer.getModel();
if (model.size() > 0) {
	int lastIndex = model.size()-1;
	String lastKey = new ArrayList<String>(model.keySet()).get(lastIndex);
	if (lastKey.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
		return model.get(lastKey);
	}
}

As you can see, it get the ModelMap used to build model data for the view part. Validated object and BindingResult correctly put should be printed to logs like that:

model equals to {article=Article {text = }, org.springframework.validation.BindingResult.article=org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'article' on field 'text': rejected value []; codes [NotEmpty.article.text,NotEmpty.text,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [article.text,text]; arguments []; default message [text]]; default message [Text can't be empty]}

After that, the values are put inside an ArrayList and the last entry key is retrieved. After that, the method argument resolver checks if this key starts with "org.springframework.validation.BindingResult." (constant value from BindingResult interface). If it's the case, the method returns found Errors instance. Otherwise, an IllegalStateException is thrown.

This article explained a little bit the world of Spring's validation. Its first part presented the validation flow, beginning with @ModelAttribute resolving and ending with validator set. The next part shown the basic Spring validators. At the last one, we saw a very popular pitfall based on placing BindingResult instance directly after validated object.


If you liked it, you should read:

📚 Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!