Comprehensive Guide to Spring Validation: RequestBody, RequestParam, and Advanced Techniques
This article provides a detailed tutorial on using Spring Validation in Spring Boot applications, covering simple and advanced usage such as requestBody and requestParam parameter checks, group validation, nested and collection validation, custom constraints, programmatic validation, fail‑fast mode, and the underlying implementation mechanisms.
Java's Bean Validation (JSR‑303) defines a standard for validating objects, but the implementation is provided by hibernate-validator , which adds annotations like @Email and @Length . Spring Validation is a thin wrapper around Hibernate Validator that enables automatic validation of Spring MVC controller parameters.
Simple Usage
For Spring Boot versions below 2.3.x, the spring-boot-starter-web starter automatically includes hibernate-validator . For newer versions, add the dependency manually:
org.hibernate
hibernate-validator
6.0.1.FinalController methods receive parameters either as requestBody (POST/PUT) or requestParam/PathVariable (GET). Both ultimately invoke Hibernate Validator; Spring Validation only adds a layer of abstraction.
requestBody Parameter Validation
Define a DTO with constraint annotations and annotate the method parameter with @Validated (or @Valid ) to trigger automatic validation. Validation failures throw MethodArgumentNotValidException , which Spring converts to a 400 response by default.
@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) {
// business logic after successful validation
return Result.ok();
}requestParam/PathVariable Parameter Validation
Annotate controller classes with @Validated and method parameters with constraints such as @Min or @Length . Validation failures raise ConstraintViolationException .
@RestController
@Validated
public class UserController {
@GetMapping("{userId}")
public Result detail(@PathVariable("userId") @Min(10000000000000000L) Long userId) {
// business logic after validation
return Result.ok();
}
@GetMapping("getByAccount")
public Result getByAccount(@Length(min = 6, max = 20) @NotNull String account) {
// business logic after validation
return Result.ok();
}
}Unified Exception Handling
Handle validation exceptions globally to return a consistent response format, often with HTTP status 200 and a custom business 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("校验失败:");
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
}
return Result.fail(BusinessCode.参数校验失败, sb.toString());
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Result handleConstraintViolationException(ConstraintViolationException ex) {
return Result.fail(BusinessCode.参数校验失败, ex.getMessage());
}
}Advanced Usage
Group Validation
Define validation groups to apply different rules for the same DTO in different scenarios (e.g., save vs. update).
@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 {}
}Use the group when validating:
@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
Validate nested objects by annotating the field with @Valid . Collections of objects can also be validated.
@Data
public class UserDTO {
@Valid
private Job job;
// other fields ...
@Data
public static class Job {
@Min(1)
private Long jobId;
@NotNull
@Length(min = 2, max = 10)
private String jobName;
}
}Collection Validation
Wrap a collection in a custom class annotated with @Valid to trigger validation of each element.
public class ValidationList
implements List
{
@Delegate
@Valid
public List
list = new ArrayList<>();
@Override
public String toString() { return list.toString(); }
}Custom Validation
Create a custom constraint annotation and its validator to implement complex business rules, such as an encrypted ID format.
@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 context) {
if (value != null) {
return PATTERN.matcher(value).find();
}
return true;
}
}Programmatic Validation
Inject javax.validation.Validator and call its API directly for validation.
@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 after the first constraint violation.
@Bean
public Validator validator() {
ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory();
return factory.getValidator();
}@Valid vs @Validated
Both trigger validation; @Validated supports validation groups, while @Valid does not.
Implementation Principles
requestBody Validation Mechanism
The RequestResponseBodyMethodProcessor resolves method arguments, reads the request body, creates a WebDataBinder , and calls validateIfApplicable() , which looks for @Validated or any annotation starting with "Valid" and then invokes binder.validate() . The binder delegates to Hibernate Validator.
Method‑Level Validation Mechanism
Spring registers an AOP advisor for beans annotated with @Validated . The MethodValidationInterceptor intercepts method calls, extracts validation groups, and uses ExecutableValidator (Hibernate Validator) to validate method parameters and return values, throwing ConstraintViolationException on failure.
In summary, both requestBody and method‑level validations ultimately rely on Hibernate Validator; Spring Validation merely provides convenient integration with Spring MVC and AOP.
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.