Close

Spring-driven Method Validation

[Updated: Aug 28, 2018, Created: Jul 1, 2018]

According to Java Bean Validation specification if validation of a method yields a non-empty set of constraint violations then javax.validation.ConstraintViolationException should be thrown wrapping the violations. The specification does not provide any implementation to throw this exception itself, instead it details the behavior that integration with interception frameworks (like Spring) should follow. The specification provides API which helps manually performing validation for methods and constructors (check out this and this examples).

Method validation can be integrated into a Spring application by registering MethodValidationPostProcessor. All target classes, which contain the methods to be validated, should also be registered as beans and should be annotated with Spring specific annotation @Validated.

Example

JavaConfig

@Configuration
@ComponentScan
public class AppConfig {
    @Bean
    public Validator validatorFactory() {
        return new LocalValidatorFactoryBean();
    }

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        return new MethodValidationPostProcessor();
    }
.....
}

Method to validate

package com.logicbig.example;

import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.FutureOrPresent;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.time.LocalDateTime;

@Validated
@Component
public class ReportTask {
  public @Pattern(regexp = "[0-3]") String createReport(@NotNull @Size(min = 3, max = 20) String name,
                                                        @NotNull @FutureOrPresent LocalDateTime startDate) {
      return "-1";
  }
}

Above method can be validated as:

       ApplicationContext context = .... 
       ReportTask task = context.getBean(ReportTask.class);
        System.out.println("-- calling ReportTask#createReport() --");
        System.out.println("-- with invalid parameters --");
        try {
            String status = task.createReport("", LocalDateTime.now().minusMinutes(30));
            System.out.println(status);
        } catch (ConstraintViolationException e) {
            for (ConstraintViolation<?> violation : e.getConstraintViolations()) {
                System.out.println(violation.getPropertyPath() + " " + violation.getMessage());
            }
        }
        System.out.println("-- with valid parameters but invalid return value --");
        try {
            String status = task.createReport("create reports", LocalDateTime.now().plusMinutes(30));
            System.out.println(status);
        } catch (ConstraintViolationException e) {
            for (ConstraintViolation<?> violation : e.getConstraintViolations()) {
                System.out.println(violation.getPropertyPath() + " " + violation.getMessage());
            }
        }

output:

-- calling ReportTask#createReport() --
-- with invalid parameters --
createReport.startDate must be a date in the present or in the future
createReport.name size must be between 3 and 20
-- with valid parameters but invalid return value --
createReport.<return value> must match "[0-3]"

We compiled above classes with -parameter flag to include parameter names in the compiled classes (check out this).

Cascaded validation

Let's see another example doing cascaded validation using @Valid annotation (also check out this tutorial).

public class User {
  @NotEmpty
  private String name;
  @Email
  private String email;
    .............
}
@Component
@Validated
public class UserTask {

  public void registerUser(@NotNull @Valid User user){
      System.out.println("registering user: "+ user);
  }
}

Validating above method:

        UserTask userTask = context.getBean(UserTask.class);
        System.out.println("-- calling UserTask#registerUser() --");
        User user = new User();
        user.setEmail("tony-example.com");
        try {
            userTask.registerUser(user);
        } catch (ConstraintViolationException e) {
            for (ConstraintViolation<?> violation : e.getConstraintViolations()) {
                System.out.println(violation.getPropertyPath() + " " + violation.getMessage());
            }
        }
-- calling UserTask#registerUser() --
registerUser.user.name must not be empty
registerUser.user.email must be a well-formed email address

Also check out all predefined constraint annotations here.

Complete main class

@Configuration
@ComponentScan
public class AppConfig {
  @Bean
  public Validator validatorFactory() {
      return new LocalValidatorFactoryBean();
  }

  @Bean
  public MethodValidationPostProcessor methodValidationPostProcessor() {
      return new MethodValidationPostProcessor();
  }

  public static void main(String[] args) {
      ApplicationContext context =
              new AnnotationConfigApplicationContext(AppConfig.class);

      ReportTask task = context.getBean(ReportTask.class);
      System.out.println("-- calling ReportTask#createReport() --");
      System.out.println("-- with invalid parameters --");
      try {
          String status = task.createReport("", LocalDateTime.now().minusMinutes(30));
          System.out.println(status);
      } catch (ConstraintViolationException e) {
          for (ConstraintViolation<?> violation : e.getConstraintViolations()) {
              System.out.println(violation.getPropertyPath() + " " + violation.getMessage());
          }
      }
      System.out.println("-- with valid parameters but invalid return value --");
      try {
          String status = task.createReport("create reports", LocalDateTime.now().plusMinutes(30));
          System.out.println(status);
      } catch (ConstraintViolationException e) {
          for (ConstraintViolation<?> violation : e.getConstraintViolations()) {
              System.out.println(violation.getPropertyPath() + " " + violation.getMessage());
          }
      }

      UserTask userTask = context.getBean(UserTask.class);
      System.out.println("-- calling UserTask#registerUser() --");
      User user = new User();
      user.setEmail("tony-example.com");
      try {
          userTask.registerUser(user);
      } catch (ConstraintViolationException e) {
          for (ConstraintViolation<?> violation : e.getConstraintViolations()) {
              System.out.println(violation.getPropertyPath() + " " + violation.getMessage());
          }
      }
  }
}
-- calling ReportTask#createReport() --
-- with invalid parameters --
createReport.name size must be between 3 and 20
createReport.startDate must be a date in the present or in the future
-- with valid parameters but invalid return value --
createReport.<return value> must match "[0-3]"
-- calling UserTask#registerUser() --
registerUser.user.name must not be empty
registerUser.user.email must be a well-formed email address

Specifying validation groups

The attribute @Validated#value can be used to specify one or more validation groups . Check out Grouping constraints tutorial to understand what validation group is. The value() attribute can be assigned to an array of group classes. This is equivalent to using javax.validation.Validation#validate(T object, Class<?>... groups).

Example Project

Dependencies and Technologies Used:

  • spring-context 5.0.7.RELEASE: Spring Context.
  • hibernate-validator 6.0.10.Final Hibernate Validator Engine - Relocation Artifact
  • javax.el 3.0.1-b09 Expression Language 3.0
  • JDK 10
  • Maven 3.5.4

Spring Driven Method Validation Example Select All Download
  • spring-driven-method-validation
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • ReportTask.java

    See Also