Close

Spring-driven Method Validation

[Last Updated: Oct 17, 2022]

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. This exception should wrap the violation details. 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) {
         printValidationError(e);
      }
      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) {
          printValidationError(e);
      }

      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) {
           printValidationError(e);
      }
  }

  private static void printValidationError(ConstraintViolationException e) {
      System.err.println("validation errors:");
      e.getConstraintViolations().stream().sorted(Comparator.comparing(v->v.getPropertyPath().toString()))
       .map(v -> v.getMessage()).forEach(System.out::println);
  }
}

Output

-- calling ReportTask#createReport() --
-- with invalid parameters --
size must be between 3 and 20
must be a date in the present or in the future
-- with valid parameters but invalid return value --
must match "[0-3]"
-- calling UserTask#registerUser() --
must be a well-formed email address
must not be empty
validation errors:
validation errors:
validation errors:

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.3.23 (Spring Context)
     Version Compatibility: 4.0.0.RELEASE - 5.3.23 Version List
    ×

    Version compatibilities of spring-context with this example:

    • 4.0.0.RELEASE
    • 4.0.1.RELEASE
    • 4.0.2.RELEASE
    • 4.0.3.RELEASE
    • 4.0.4.RELEASE
    • 4.0.5.RELEASE
    • 4.0.6.RELEASE
    • 4.0.7.RELEASE
    • 4.0.8.RELEASE
    • 4.0.9.RELEASE
    • 4.1.0.RELEASE
    • 4.1.1.RELEASE
    • 4.1.2.RELEASE
    • 4.1.3.RELEASE
    • 4.1.4.RELEASE
    • 4.1.5.RELEASE
    • 4.1.6.RELEASE
    • 4.1.7.RELEASE
    • 4.1.8.RELEASE
    • 4.1.9.RELEASE
    • 4.2.0.RELEASE
    • 4.2.1.RELEASE
    • 4.2.2.RELEASE
    • 4.2.3.RELEASE
    • 4.2.4.RELEASE
    • 4.2.5.RELEASE
    • 4.2.6.RELEASE
    • 4.2.7.RELEASE
    • 4.2.8.RELEASE
    • 4.2.9.RELEASE
    • 4.3.0.RELEASE
    • 4.3.1.RELEASE
    • 4.3.2.RELEASE
    • 4.3.3.RELEASE
    • 4.3.4.RELEASE
    • 4.3.5.RELEASE
    • 4.3.6.RELEASE
    • 4.3.7.RELEASE
    • 4.3.8.RELEASE
    • 4.3.9.RELEASE
    • 4.3.10.RELEASE
    • 4.3.11.RELEASE
    • 4.3.12.RELEASE
    • 4.3.13.RELEASE
    • 4.3.14.RELEASE
    • 4.3.15.RELEASE
    • 4.3.16.RELEASE
    • 4.3.17.RELEASE
    • 4.3.18.RELEASE
    • 4.3.19.RELEASE
    • 4.3.20.RELEASE
    • 4.3.21.RELEASE
    • 4.3.22.RELEASE
    • 4.3.23.RELEASE
    • 4.3.24.RELEASE
    • 4.3.25.RELEASE
    • 4.3.26.RELEASE
    • 4.3.27.RELEASE
    • 4.3.28.RELEASE
    • 4.3.29.RELEASE
    • 4.3.30.RELEASE
    • 5.0.0.RELEASE
    • 5.0.1.RELEASE
    • 5.0.2.RELEASE
    • 5.0.3.RELEASE
    • 5.0.4.RELEASE
    • 5.0.5.RELEASE
    • 5.0.6.RELEASE
    • 5.0.7.RELEASE
    • 5.0.8.RELEASE
    • 5.0.9.RELEASE
    • 5.0.10.RELEASE
    • 5.0.11.RELEASE
    • 5.0.12.RELEASE
    • 5.0.13.RELEASE
    • 5.0.14.RELEASE
    • 5.0.15.RELEASE
    • 5.0.16.RELEASE
    • 5.0.17.RELEASE
    • 5.0.18.RELEASE
    • 5.0.19.RELEASE
    • 5.0.20.RELEASE
    • 5.1.0.RELEASE
    • 5.1.1.RELEASE
    • 5.1.2.RELEASE
    • 5.1.3.RELEASE
    • 5.1.4.RELEASE
    • 5.1.5.RELEASE
    • 5.1.6.RELEASE
    • 5.1.7.RELEASE
    • 5.1.8.RELEASE
    • 5.1.9.RELEASE
    • 5.1.10.RELEASE
    • 5.1.11.RELEASE
    • 5.1.12.RELEASE
    • 5.1.13.RELEASE
    • 5.1.14.RELEASE
    • 5.1.15.RELEASE
    • 5.1.16.RELEASE
    • 5.1.17.RELEASE
    • 5.1.18.RELEASE
    • 5.1.19.RELEASE
    • 5.1.20.RELEASE
    • 5.2.0.RELEASE
    • 5.2.1.RELEASE
    • 5.2.2.RELEASE
    • 5.2.3.RELEASE
    • 5.2.4.RELEASE
    • 5.2.5.RELEASE
    • 5.2.6.RELEASE
    • 5.2.7.RELEASE
    • 5.2.8.RELEASE
    • 5.2.9.RELEASE
    • 5.2.10.RELEASE
    • 5.2.11.RELEASE
    • 5.2.12.RELEASE
    • 5.2.13.RELEASE
    • 5.2.14.RELEASE
    • 5.2.15.RELEASE
    • 5.2.16.RELEASE
    • 5.2.17.RELEASE
    • 5.2.18.RELEASE
    • 5.2.19.RELEASE
    • 5.2.20.RELEASE
    • 5.2.21.RELEASE
    • 5.2.22.RELEASE
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.3.3
    • 5.3.4
    • 5.3.5
    • 5.3.6
    • 5.3.7
    • 5.3.8
    • 5.3.9
    • 5.3.10
    • 5.3.11
    • 5.3.12
    • 5.3.13
    • 5.3.14
    • 5.3.15
    • 5.3.16
    • 5.3.17
    • 5.3.18
    • 5.3.19
    • 5.3.20
    • 5.3.21
    • 5.3.22
    • 5.3.23

    Versions in green have been tested.

  • hibernate-validator 6.2.3.Final (Hibernate Validator Engine - Relocation Artifact)
  • javax.el 3.0.1-b09 (Expression Language 3.0)
  • JDK 8
  • Maven 3.8.1

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

    See Also