We are the Dev Teams of
  • brands
  • ebay_main
  • ebay
  • mobile
<
>
BLOG

Customized form validation with spring web mvc and @Valid

by André Charton

In this article I would like to explain how spring supports customized form validation (using @Valid annotation).

In the example we validate two fields that depend on each another.  If the poster is a commercial user, then the VAT number has to be validated.

To get started we first need to create a submit form that contains detailed information:

@ValidVatId( 
  messageWrong = "user.vat.wrong", 
  messageNotNull = "user.vat.missing", 
  vatFieldName = "vatId") 
public class UserInformationForm { 
   private boolean commercial; 
   private String vatId; 

   public String getVatId() {
      return vatId;
   }
   public void setVatId(String value) {
      this.vatId = value;
   }
   public boolean isCommercial() {
      return commercial;
   }
   public void setCommercial(boolean value) {
      this.commercial = value;
   }
}

The form is annotated with specific validation information. This is our customized VAT validation annotation, see below.

You can edit the target type (@Target) depending on what level/part of your form you need to validate. We also add String parameters to support specific validation error messages (messageWrong and messageNotNull).

@Documented
@Constraint(validatedBy = UserVatValidator.class)
@Target( { ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidVatId {

    String message() default "";

    String messageWrong() default "";

    String messageNotNull() default "";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String vatFieldName() default "vatId";
}

The validator @interface points to our customized validator class UserVatValidator. This is a spring bean (@Component). In this example it looks like the following ...

@Component
class UserVatValidator implements ConstraintValidator<ValidVatId, UserInformationForm> {

    private final VatValidationService vatValidationService;
    private String fieldName;
    private String messageKeyWrong;
    private String messageNotNull;

    @Autowired
    public UserInvoiceDetailsValidator(VatValidationService vatValidationService) {
        this.vatValidationService = vatValidationService;
    }

    @Override
    public void initialize(ValidVatId constraintAnnotation) {
        this.fieldName = constraintAnnotation.vatFieldName();
        this.messageKeyWrong = constraintAnnotation.messageWrong();
        this.messageNotNull = constraintAnnotation.messageNotNull();
    }

    @Override
    public boolean isValid(UserInvoiceDetailsForm userDetailsForm, ConstraintValidatorContext context) {
        if(userDetailsForm.isCommercial()) {
            if (StringUtil.isBlank(userDetailsForm.getVatId())) {
                markFieldError(context, messageNotNull);
                return false;
            }

            if(!vatValidationService.validateVat(VatId.parseVat(userDetailsForm.getVatId()))) {
                markFieldError(context, messageKeyWrong);
                return false;
            }
        }
        return true;
    }

    private void markFieldError(ConstraintValidatorContext context, String key) {
        ConstraintValidatorContext.ConstraintViolationBuilder cvb = context.buildConstraintViolationWithTemplate(key);
        cvb.addNode(fieldName).addConstraintViolation();
    }
}

Now we add the two validation error messages to our messageSource.properties file:

user.vat.wrong=Please verfiy your vat id.

user.vat.missing=Vat id is missing for commercial users.

And finally we annotate the form in the submit request method for validation:

...

 @RequestMapping(value = ShowUserInvoiceDetailsUrl.PATH, method = RequestMethod.POST)
 public String submitForm(
   User user,
   @ModelAttribute("userDetailsFrom") @Valid UserInformationForm userDetailsForm,
   BindingResult bindingResult,
   ModelMap modelMap) {

        if (bindingResult.hasErrors()) {
            return "editUser.jsp";
        }

        detailsService.submit(toDomainModel(user, userInvoiceDetailsForm));

        modelMap.put("successMsg", "user.form.submit.okay");

        return "editUserResult.jsp";
    }
...

That's it - thank you for reading.

Comments are welcome.

?>