Comprehensive Guide to Spring Validation: Best Practices, Advanced Usage, and Implementation Details
This article provides an in‑depth tutorial on Spring Validation, covering basic usage, dependency setup, requestBody and requestParam validation, DTO constraints, group and nested validation, collection handling, custom validators, programming‑style validation, fail‑fast mode, and the underlying implementation mechanisms in Spring MVC.
This article offers a thorough introduction to Spring Validation , explaining its purpose, how it builds on the JSR‑303 Bean Validation API, and why it is essential for validating request parameters in Spring MVC applications.
Simple Usage
The article starts with the required Maven dependency. For Spring Boot versions below 2.3.x , the spring-boot-starter-web starter automatically includes hibernate-validator . For newer versions, the dependency must be added manually.
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.1.Final</version>
</dependency>requestBody Parameter Validation
When a POST or PUT request uses @RequestBody , a DTO object receives the data. Adding @Validated (or @Valid ) on the DTO triggers automatic validation. Example:
@Data
public class UserDTO {
@NotNull
@Length(min = 2, max = 10)
private String userName;
@NotNull
@Length(min = 6, max = 20)
private String account;
@NotNull
@Length(min = 6, max = 20)
private String password;
} @PostMapping("/save")
public Result saveUser(@RequestBody @Validated UserDTO userDTO) {
return Result.ok();
}If validation fails, Spring throws MethodArgumentNotValidException , which is translated to a 400 Bad Request response by default.
requestParam / PathVariable Validation
For GET requests, parameters are passed via @RequestParam or @PathVariable . Validation can be applied directly on method arguments or on a DTO. When using method‑level annotations, the controller class must be annotated with @Validated and each parameter can be constrained with annotations such as @Min or @Length .
@RequestMapping("/api/user")
@RestController
@Validated
public class UserController {
@GetMapping("{userId}")
public Result detail(@PathVariable("userId") @Min(10000000000000000L) Long userId) {
// ...
return Result.ok();
}
@GetMapping("getByAccount")
public Result getByAccount(@Length(min = 6, max = 20) @NotNull String account) {
// ...
return Result.ok();
}
}Group Validation
Different validation rules can be applied to the same DTO in different scenarios using validation groups. The DTO defines marker interfaces (e.g., Save and Update ) and assigns them to constraint annotations via the groups attribute.
@Data
public class UserDTO {
@Min(value = 10000000000000000L, groups = Update.class)
private Long userId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String userName;
// other fields …
public interface Save {}
public interface Update {}
}Controller methods then specify the group to validate:
@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
If a DTO contains another object, the nested object must be annotated with @Valid to trigger its validation. This also works with collections when the collection field is marked with @Valid .
@Data
public class UserDTO {
// … other fields …
@NotNull(groups = {Save.class, Update.class})
@Valid
private Job job;
}
@Data
public static class Job {
@Min(value = 1, groups = Update.class)
private Long jobId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String jobName;
}Collection Validation
To validate each element of a collection, wrap the list in a custom class that delegates to a List and is annotated with @Valid . Lombok’s @Delegate (v1.18.6+) can be used for delegation.
public class ValidationList
implements List
{
@Delegate
@Valid
public List
list = new ArrayList<>();
@Override
public String toString() {
return list.toString();
}
}Controller usage:
@PostMapping("/saveList")
public Result saveList(@RequestBody @Validated(UserDTO.Save.class) ValidationList
userList) {
return Result.ok();
}Custom Validation
For business‑specific rules, a custom constraint annotation and its validator can be created. Example of an @EncryptId annotation that validates a hexadecimal string of length 32‑256:
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@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 context) {
if (value != null) {
return PATTERN.matcher(value).find();
}
return true;
}
}Programmatic Validation
When annotation‑based validation is insufficient, the javax.validation.Validator can be injected and invoked 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
By default, all constraints are evaluated. Configuring a Validator bean with failFast(true) makes validation stop at the first violation.
@Bean
public Validator validator() {
ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory();
return factory.getValidator();
}@Valid vs @Validated
The article includes a visual comparison (image) showing the differences between the two annotations, emphasizing that @Validated supports validation groups while @Valid does not.
Implementation Principles
requestBody Validation Mechanism
Spring MVC uses RequestResponseBodyMethodProcessor to resolve @RequestBody arguments. Inside resolveArgument() , it calls validateIfApplicable() , which checks for @Validated or any annotation starting with "Valid" and then invokes WebDataBinder.validate() . The binder ultimately delegates to the Hibernate Validator.
Method‑Level Validation Mechanism
Method‑level validation is implemented via AOP. MethodValidationPostProcessor registers an AnnotationMatchingPointcut for @Validated and creates a MethodValidationInterceptor . The interceptor uses ExecutableValidator to validate method parameters and return values, again relying on Hibernate Validator.
In summary, both request‑body and method‑level validations are thin wrappers around Hibernate Validator, with Spring providing convenient annotation‑driven integration.
Source: juejin.cn/post/6856541106626363399
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.