Comprehensive Guide to Spring Validation: Best Practices and Implementation Principles
This article provides an in‑depth tutorial on Spring Validation, covering simple usage, requestBody and requestParam/PathVariable validation, unified exception handling, advanced techniques such as group, nested, collection and custom validation, programmatic validation, fail‑fast mode, and the underlying implementation mechanisms within Spring MVC and Hibernate Validator.
Simple Usage
The Java API specification (JSR‑303) defines a standard validation‑api but does not provide an implementation. Hibernate Validator implements this specification and adds annotations such as @Email and @Length . Spring Validation is a secondary wrapper around Hibernate Validator that enables automatic parameter validation in Spring MVC. For a Spring Boot project, add the dependency as shown below (Spring Boot < 2.3.x includes it automatically; for newer versions add it manually):
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.1.Final</version>
</dependency>In a web service, controller parameters must be validated to prevent illegal input from affecting business logic. Requests are usually of two types: POST/PUT with requestBody and GET with requestParam/PathVariable .
1. requestBody Parameter Validation
For POST/PUT requests, use a DTO object annotated with @Validated . If validation fails, Spring throws MethodArgumentNotValidException , which is converted to a 400 Bad Request response.
@Data
public class UserDTO {
private Long userId;
@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) {
// Business logic executes only after successful validation
return Result.ok();
}2. requestParam/PathVariable Parameter Validation
For GET requests, you can still use DTOs or place each parameter directly in the method signature. The controller class must be annotated with @Validated , and each parameter should have constraint annotations such as @Min . Validation failures raise ConstraintViolationException .
@RequestMapping("/api/user")
@RestController
@Validated
public class UserController {
@GetMapping("{userId}")
public Result detail(@PathVariable("userId") @Min(10000000000000000L) Long userId) {
UserDTO userDTO = new UserDTO();
userDTO.setUserId(userId);
// ... set other fields
return Result.ok(userDTO);
}
@GetMapping("getByAccount")
public Result getByAccount(@Length(min = 6, max = 20) @NotNull String account) {
UserDTO userDTO = new UserDTO();
// ... set fields
return Result.ok(userDTO);
}
}3. Unified Exception Handling
Both MethodArgumentNotValidException and ConstraintViolationException can be handled globally to return a friendly response, often with HTTP status 200 and a business error code.
@RestControllerAdvice
public class CommonExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
BindingResult bindingResult = ex.getBindingResult();
StringBuilder sb = new StringBuilder("Validation failed:");
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
}
return Result.fail(BusinessCode.PARAMETER_VALIDATION_FAILED, sb.toString());
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Result handleConstraintViolationException(ConstraintViolationException ex) {
return Result.fail(BusinessCode.PARAMETER_VALIDATION_FAILED, ex.getMessage());
}
}Advanced Usage
1. Group Validation
When the same DTO is used in different methods with different validation rules, define groups on constraint annotations and specify the group on @Validated .
@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 with group definitions
public interface Save {}
public interface Update {}
} @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();
}2. Nested Validation
If a DTO field is another object, annotate the field with @Valid to trigger validation of the nested object.
@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;
// ... other fields
}
}3. Collection Validation
To validate each element of a JSON array, wrap the collection in a custom class and annotate the collection field with @Valid .
public class ValidationList
implements List
{
@Delegate // Lombok annotation
@Valid
public List
list = new ArrayList<>();
@Override
public String toString() {
return list.toString();
}
}
@PostMapping("/saveList")
public Result saveList(@RequestBody @Validated(UserDTO.Save.class) ValidationList
userList) {
return Result.ok();
}4. Custom Validation
Create a custom constraint annotation and its validator to handle special rules, e.g., an encrypted ID consisting of 32‑256 hexadecimal characters.
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {EncryptIdValidator.class})
public @interface EncryptId {
String message() default "Invalid encrypted ID format";
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;
}
}5. Programmatic Validation
Inject javax.validation.Validator and call its API directly when annotation‑based validation is not suitable.
@Autowired
private javax.validation.Validator globalValidator;
@PostMapping("/saveWithCodingValidate")
public Result saveWithCodingValidate(@RequestBody UserDTO userDTO) {
Set
> validate = globalValidator.validate(userDTO, UserDTO.Save.class);
if (!validate.isEmpty()) {
for (ConstraintViolation
v : validate) {
System.out.println(v);
}
}
return Result.ok();
}6. Fail‑Fast Mode
Configure Hibernate Validator to stop after the first constraint violation.
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}7. Difference Between @Valid and @Validated
Both trigger validation, but @Validated supports validation groups while @Valid does not. (See the accompanying image in the original article.)
Implementation Principles
1. requestBody Validation Mechanism
Spring MVC uses RequestResponseBodyMethodProcessor to resolve @RequestBody arguments. Inside resolveArgument() , after reading the request body, it calls validateIfApplicable() , which checks for @Validated or any annotation whose name starts with "Valid" and then invokes binder.validate() . The binder ultimately delegates to Hibernate Validator.
2. Method‑Level Validation Mechanism
Method‑level validation is implemented via AOP. MethodValidationPostProcessor registers an advisor for beans annotated with @Validated . The advisor uses MethodValidationInterceptor , which calls ExecutableValidator.validateParameters() before method execution and validateReturnValue() after execution, both delegating to Hibernate Validator.
Project source code: https://github.com/chentianming11/spring-validation
Final Note
The author invites readers to follow the public account “码猿技术专栏” to obtain PDF collections of Spring Cloud, Spring Boot, and MyBatis advanced tutorials, and encourages likes, shares, and comments to support future content creation.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.