Spring MVC: Controller level model binding validation

Salad Lam - Jan 31 - - Dev Community

Notice

I wrote this article and was originally published on Qiita on 9 July 2021.


To perform model binding validation of controller, one of the solution is register validator on controller.

1. Write your own validator

You validator should extend org.springframework.validation.Validator interface. Below is a example.

public class MessageDtoValidator implements Validator {

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

    @Override
    public void validate(Object target, Errors errors) {
        // target is model being checked and errors register into errors instance
        ValidationUtils.rejectIfEmpty(errors, "publishDate", "publishDate.empty");
        ValidationUtils.rejectIfEmpty(errors, "description", "description.empty");
    }

}
Enter fullscreen mode Exit fullscreen mode

2. Register your validator

You should register your validator on function annotated with org.springframework.web.bind.annotation.InitBinder annotation with org.springframework.validation.DataBinder.setValidator(Validator) function.

// Call everytime when recevies request
@InitBinder
public void initBinder(WebDataBinder binder) {
    ...
    binder.setValidator(new MessageDtoValidator())
    ...
}
Enter fullscreen mode Exit fullscreen mode

org.springframework.validation.Validator.supports(Class<?>) of your validator will be called when calling setValidator() to verify is your validator supports model binded.

3. Specify model argument you want to verify and define corresponding action

You should add javax.validation.Valid (or org.springframework.validation.annotation.Validated) annotation in addition with org.springframework.web.bind.annotation.ModelAttribute annotation on model argument you want to verify. And specify actions when binding error is found.

@PostMapping("/{messageId}/save")
public String saveEditMessage(@PathVariable("messageId") long id, @Valid @ModelAttribute MessageDto message, BindingResult errors) {
    if (errors.hasErrors()) {
        // your action
        ...
    }
}
Enter fullscreen mode Exit fullscreen mode

After binding model, framework will check if validation annotation exists on model argument by org.springframework.web.method.annotation.ModelAttributeMethodProcessor.determineValidationHints(Annotation) function.

/**
 * Determine any validation triggered by the given annotation.
 * @param ann the annotation (potentially a validation annotation)
 * @return the validation hints to apply (possibly an empty array),
 * or {@code null} if this annotation does not trigger any validation
 * @since 5.1
 */
@Nullable
private Object[] determineValidationHints(Annotation ann) {
    Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
    if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
        Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
        if (hints == null) {
            return new Object[0];
        }
        return (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
    }
    return null;
}
Enter fullscreen mode Exit fullscreen mode

And then actual validation logic org.springframework.validation.Validator.validate(Object, Errors) will be called by org.springframework.web.method.annotation.ModelAttributeMethodProcessor.validateIfApplicable(WebDataBinder, MethodParameter) function.

Example

You can find actual implementation from my notice board example application.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player