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 ProjectDependencies and Technologies Used: - spring-context 6.1.2 (Spring Context)
Version Compatibility: 4.0.0.RELEASE - 6.1.2 Version compatibilities of spring-context with this example: 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
|