Java Validation API (JSR-303)

 This page contains information and reference about the following topics/questions/how to's

  1. Why would I want to use the Java Validation API (JSR-303) over Spring's built in mechansim
  2. Could I use Spring validation mechnaism in conjunction with JSR-303
  3. Does JSR-303 limit me to just peform validation on the Controller layer like Spring does
  4. How do I turn on the Java Validation API in my Spring application
  5. Can I share my validators with others as a service

Architecture Overview

 To gain an understanding of Java Validation API please See the following articles:

Best Practices & How To's

Only use available API under this package

 Only use available API under this package

  • *
    import javax.validation.*;
    *
    
  • Hibernate Validator was the reference implementation for jsr-303
    • although the latest version of Hibernate Validator offers more (please refrain from using anything not under the package listed above)

Configuring jsr-303 within Spring

 
  • include libraries in the class path
  • include the following in your spring context.xml file
    • *
      
      ...
      <bean
          class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
          <property name="webBindingInitializer">
              <!-- Configures Spring MVC DataBinder instances -->
              <bean
                  class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
                  <property name="validator" ref="validator" />
              </bean>
          </property>
      </bean>
      
      <!-- Creates the JSR-303 Validator -->
      <bean id="validator"
          class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
      </bean>
      ...
      
      *
      

@Valid

 

@Valid: Most Simple and Basic Usage, no need to create a Validator class

  • annotate your entities with validation rules
    • example
      • *
        
        ...
        @Entity
        @Table(schema = "COA_DB", name = "FUND")
        public class Fund implements Serializable {
        
            /**
             * IFOP
             * javax.validation.constraints.
             */
            @Size(min=4,max=10,message="Index size must be between 4 and 10")
            @Pattern(regexp = ".*(?=.{4,})(?=.*[\\d[a-z][A-Z]]{4,}).*", 
                            message = "At least 4 digit/alphabetic chars is required")
            String indx;
        
        }
        ...
        
        *
        
  • call @Valid on your modelAttribute, that's it
    • *
      
      @Autowired
      private Validator validator;
      
      @RequestMapping(value = "test.html", method = RequestMethod.POST)
      public ModelAndView fundIndexFormSubmit(
              @Valid @ModelAttribute("fundCmd") FundCmd fundCmd,
              BindingResult bindingResults) {
          ...
          if (bindingResults.hasErrors()) {
              mav.setViewName("test-invalid.jsp");
          }else{
          ...
          }
          ...
      }
      
      *
      

Validation Groups

 

 Validation Groups: See Validating groups

How to use validation groups with @Valid annotation

 

 How to use validation groups with @Valid annotation

  • you can NOT at the moment, hopefully Spring and Java will implement this in the near future as this has been listed in their list of issues at the moment
  • How to use validation groups then?
    • Fortunately, you can use the pragmatic way to achieve the same results
    • *
      
      @Controller("FundController")
      public class FundController {
      
          private Validator validator;
      
          @RequestMapping(value = "test.html", method = RequestMethod.POST)
          public ModelAndView fundIndexFormSubmit(
                  @ModelAttribute("fundCmd") FundCmd fundCmd,
                  BindingResult bindingResults) {
              ...
              validator = Validation.buildDefaultValidatorFactory().getValidator();
              //setting the group IndexCheck
              Set<ConstraintViolation<fundCmd>> violations = validator.validate(fundCmd, IndexCheck.class);
              for (ConstraintViolation<IFOPCriteria> constraintViolation : violations) {
                  errors.add(...;
              }
              //check if errors exist
              if (errors.size()!=0) {
                  mav.setViewName("test-invalid.jsp");
              }else{
              ...
              }
              ...
          }    
      ...
      
      * 
      

 How to use Validation Groups and Spring Validator in conjunction

 

 How to use Validation Groups and Spring Validator in conjunction

  • previous approach is fine but we loose integration with Spring Validation, to overcome that we have to create a Spring Validator and inject a Java Validator in it
    1. create a Spring Validator Class
    2. Annotate the class with @Component
    3. Inject a Java javax.validation.Validator into your Spring Validator
    4. In your Controller, inject the newely created Spring Validator
    5. In your Controller, Pass groups to your validator constructor if any
  • example:
  • *
    
    ...
    import javax.validation.ConstraintViolation;
    import javax.validation.Validator;
    import javax.validation.metadata.ConstraintDescriptor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.validation.Errors;
    
    @Component
    public class FundValidator implements org.springframework.validation.Validator{
    
        Class<?>[] groups;
    
        @Autowired
        private Validator validator;
    
        public FundValidator() {
            super();
        }
    
        public FundValidator(Class<?>[] groups) {
            super();
            this.groups = groups;
        }
    
        public boolean supports(Class<?> clazz) {
            return Fund.class.isAssignableFrom(clazz);
        }
    
        public void validate(Object target, Errors errors) {
            Fund fund = (Fund)target;
            Set<ConstraintViolation<Fund>> constraintViolations = validator.validate(fund,groups);
            for (ConstraintViolation<Fund> constraintViolation : constraintViolations) {
                ConstraintDescriptor<?> test = constraintViolation.getConstraintDescriptor();
                String propertyPath = constraintViolation.getPropertyPath().toString();
                String message = constraintViolation.getMessage();
                errors.rejectValue(propertyPath, "", message);
            }
        }
    
        public Class<?>[] getGroups() {
            return groups;
        }
    
        public void setGroups(Class<?>[] groups) {
            this.groups = groups;
        }
    
    
    }
    *
    @Controller("FundController")
    public class FundController {
    
    @Autowired
    private FundValidator fundValidator;
    
        @RequestMapping(value = "test.html", method = RequestMethod.POST)
        public ModelAndView fundIndexFormSubmit(
                @ModelAttribute("fundCmd") FundCmd fundCmd,
                BindingResult bindingResults) {
            ...
            Class<?>[] groups = { IndexCheck.class };
            // validate
            fundValidator.setGroups(groups);
            fundValidator.validate(bin, bindingResults);
            if (bindingResults.hasErrors()) {
                mav.setViewName("test-invalid.jsp");
            }else{
            ...
            }
            ...
        }
    ...
    }
    

 Governance

  • Share your custom validators within your group if possible (i.e. IFOPValidator ...)

  • Only use validation API from Java i.e. import javax.validation.*;

    • do NOT use the extended validators that the API implementation offers
    • in our case since we user Hibernate's implementation the following package should be avoided: import org.hibernate.validator.*;

Advanced

See Hibernate Validator (JSR 303 Reference Implementation) Reference Guide

References