Comprehensive Guide to Spring Validation: Parameter Checks, Groups, Nested Objects, Collections, Custom Constraints, and Implementation Details
This article provides a thorough tutorial on using Spring Validation for request parameter checking, covering simple usage, dependency setup, requestBody and requestParam validation, group and nested validation, collection handling, custom constraints, programmatic validation, fail‑fast mode, and the underlying implementation in Spring MVC and Hibernate Validator.
Simple Usage
Spring provides the validation-api (JSR‑303) standard for bean validation, while hibernate-validator implements this standard and adds annotations such as @Email and @Length . Spring Validation is a thin wrapper around Hibernate Validator that integrates with Spring MVC for automatic request parameter validation.
Dependency Introduction
If you use Spring Boot version < 2.3.x, the spring-boot-starter-web starter automatically brings in hibernate-validator . For versions >= 2.3.x you need to add the dependency manually:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.1.Final</version>
</dependency>requestBody Parameter Validation
For POST / PUT requests, define a DTO and annotate it with @Validated (or @Valid ) on the controller method:
@PostMapping("/save")
public Result saveUser(@RequestBody @Validated UserDTO userDTO) {
// business logic after successful validation
return Result.ok();
}If validation fails, Spring throws MethodArgumentNotValidException , which is translated to a 400 Bad Request response.
requestParam / PathVariable Validation
For GET requests, use @RequestParam or @PathVariable with constraint annotations. When many parameters are involved, wrap them in a DTO and annotate the controller class with @Validated :
@GetMapping("{userId}")
public Result detail(@PathVariable("userId") @Min(10000000000000000L) Long userId) {
// business logic after validation
return Result.ok();
}Group Validation
Define validation groups (e.g., Save and Update ) on DTO fields to apply different rules in different scenarios, and specify the group in the controller method:
@PostMapping("/save")
public Result saveUser(@RequestBody @Validated(UserDTO.Save.class) UserDTO userDTO) {
return Result.ok();
}
@PostMapping("/update")
public Result updateUser(@RequestBody @Validated(UserDTO.Update.class) UserDTO userDTO) {
return Result.ok();
}Nested Validation
When a DTO contains another object, annotate the nested field with @Valid so that its constraints are also validated:
@Data
public class UserDTO {
@NotNull @Length(min = 2, max = 10) private String userName;
@Valid private Job job; // nested object
}
@Data
public static class Job {
@Min(1) private Long jobId;
@NotNull @Length(min = 2, max = 10) private String jobName;
}Collection Validation
To validate each element of a collection, wrap the list in a custom class annotated with @Valid and @Delegate (requires Lombok >= 1.18.6):
public class ValidationList
implements List
{
@Delegate @Valid public List
list = new ArrayList<>();
@Override public String toString() { return list.toString(); }
}Use it in a controller method:
@PostMapping("/saveList")
public Result saveList(@RequestBody @Validated(UserDTO.Save.class) ValidationList
userList) {
return Result.ok();
}Custom Constraint
Create a custom annotation (e.g., @EncryptId ) and implement ConstraintValidator to define the validation logic:
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = EncryptIdValidator.class)
public @interface EncryptId {
String message() default "加密id格式错误";
Class
[] groups() default {};
Class
[] payload() default {};
} public class EncryptIdValidator implements ConstraintValidator
{
private static final Pattern PATTERN = Pattern.compile("^[a-f\\d]{32,256}$");
@Override public boolean isValid(String value, ConstraintValidatorContext ctx) {
if (value != null) {
return PATTERN.matcher(value).find();
}
return true;
}
}Programmatic Validation
Inject javax.validation.Validator and call its API directly:
@Autowired private javax.validation.Validator globalValidator;
@PostMapping("/saveWithCodingValidate")
public Result saveWithCodingValidate(@RequestBody UserDTO userDTO) {
Set
> violations =
globalValidator.validate(userDTO, UserDTO.Save.class);
if (!violations.isEmpty()) {
violations.forEach(v -> System.out.println(v));
}
return Result.ok();
}Fail‑Fast Mode
Configure Hibernate Validator to stop at the first constraint violation:
@Bean
public Validator validator() {
ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory();
return factory.getValidator();
}Implementation Details
Spring MVC’s RequestResponseBodyMethodProcessor reads the request body, creates a WebDataBinder , and invokes binder.validate() . The binder delegates to HibernateValidator via targetValidator.validate() . Method‑level validation is handled by MethodValidationPostProcessor , which registers an AOP advisor that uses MethodValidationInterceptor to call ExecutableValidator.validateParameters() and validateReturnValue() on the underlying Hibernate Validator.
All validation paths ultimately rely on Hibernate Validator; Spring Validation merely provides convenient annotations and integration with the MVC lifecycle.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.