Close

Spring-driven Method Validation

[Last Updated: Dec 22, 2023]

According to Java Bean Validation specification if validation of a method yields a non-empty set of constraint violations then jakarta.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 jakarta.validation.constraints.FutureOrPresent;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.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.out.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 --
validation errors:
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 --
validation errors:
must match "[0-3]"
-- calling UserTask#registerUser() --
validation errors:
must be a well-formed email address
must not be empty

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 6.1.2 (Spring Context)
     Version Compatibility: 4.0.0.RELEASE - 6.1.2Version 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.2.23.RELEASE
    • 5.2.24.RELEASE
    • 5.2.25.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
    • 5.3.24
    • 5.3.25
    • 5.3.26
    • 5.3.27
    • 5.3.28
    • 5.3.29
    • 5.3.30
    • 5.3.31
    • Compatible Java Version: 17+
    • 6.0.0
    • 6.0.1
    • 6.0.2
    • 6.0.3
    • 6.0.4
    • 6.0.5
    • 6.0.6
    • 6.0.7
    • 6.0.8
    • 6.0.9
    • 6.0.10
    • 6.0.11
    • 6.0.12
    • 6.0.13
    • 6.0.14
    • 6.0.15
    • 6.1.0
    • 6.1.1
    • 6.1.2

    Versions in green have been tested.

  • hibernate-validator 8.0.1.Final (Hibernate's Jakarta Bean Validation reference implementation)
  • expressly 5.0.0 (Jakarta Expression Language Implementation)
  • JDK 17
  • 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