Comprehensive Guide to Spring Validation in Spring Boot
This guide explains how to configure and use Spring Validation (JSR‑303) in Spring Boot, covering dependency setup, @Valid vs @Validated, request body and parameter validation, group, nested and collection checks, custom constraints, programmatic validation, fail‑fast mode, and underlying implementation details.
This article provides an in‑depth tutorial on using Spring Validation (JSR‑303) with Spring Boot, covering basic usage, dependency configuration, request body and request parameter validation, group validation, nested and collection validation, custom constraints, programmatic validation, fail‑fast mode, and the differences between @Valid and @Validated .
Simple usage
Spring Validation is a wrapper around Hibernate Validator, which implements the JSR‑303 validation‑api . Adding the starter spring-boot-starter-web pulls in hibernate-validator automatically for Spring Boot < 2.3.x; for newer versions you must add the dependency manually:
org.hibernate
hibernate-validator
6.0.1.FinalFor web services, validate request parameters in the Controller layer. POST/PUT requests use @RequestBody , while GET requests use @RequestParam or @PathVariable .
RequestBody validation
Define a DTO with constraint annotations (e.g., @NotNull , @Length ) and annotate the controller method parameter with @Validated (or @Valid ) to trigger automatic validation. Validation failures raise MethodArgumentNotValidException , which Spring translates to a 400 response.
@PostMapping("/save")
public Result saveUser(@RequestBody @Validated UserDTO userDTO) {
// business logic after successful validation
return Result.ok();
}RequestParam / PathVariable validation
Annotate method parameters directly, or apply @Validated at the controller class level for grouped validation. Validation failures raise ConstraintViolationException .
@GetMapping("{userId}")
public Result detail(@PathVariable("userId") @Min(10000000000000000L) Long userId) {
// business logic
return Result.ok();
}Group validation
Define marker interfaces (e.g., Save , Update ) and assign them to constraint annotations via the groups attribute. Then specify the group in @Validated(Group.class) to apply different rules for create vs. update operations.
@PostMapping("/save")
public Result saveUser(@RequestBody @Validated(UserDTO.Save.class) UserDTO userDTO) { ... }
@PostMapping("/update")
public Result updateUser(@RequestBody @Validated(UserDTO.Update.class) UserDTO userDTO) { ... }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;
@NotNull @Valid private Job job; // nested object
// ... 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 List in a custom class annotated with @Valid (using Lombok’s @Delegate ) to trigger element‑wise validation.
public class ValidationList
implements List
{
@Delegate @Valid public List
list = new ArrayList<>();
@Override public String toString() { return list.toString(); }
}Controller example:
@PostMapping("/saveList")
public Result saveList(@RequestBody @Validated(UserDTO.Save.class) ValidationList
userList) {
return Result.ok();
}Custom constraint
Create an annotation (e.g., @EncryptId ) and a corresponding ConstraintValidator implementation to validate encrypted IDs.
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = EncryptIdValidator.class)
public @interface EncryptId {
String message() default "Invalid encrypted 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) {
return value == null || PATTERN.matcher(value).find();
}
}Programmatic validation
Inject javax.validation.Validator and call validate() with the desired groups.
@Autowired private Validator validator;
@PostMapping("/saveWithCodingValidate")
public Result saveWithCodingValidate(@RequestBody UserDTO userDTO) {
Set
> violations = validator.validate(userDTO, UserDTO.Save.class);
if (!violations.isEmpty()) {
// handle violations
}
return Result.ok();
}Fail‑fast mode
Configure a Validator bean with failFast(true) so that validation stops at the first error.
@Bean
public Validator validator() {
ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory();
return factory.getValidator();
}@Valid vs @Validated
@Valid is a JSR‑303 annotation, supports nested validation but not groups. @Validated is a Spring annotation, supports groups but not nested validation. Both can be mixed where appropriate.
Implementation details
Spring MVC’s RequestResponseBodyMethodProcessor calls validateIfApplicable() , which looks for @Validated or any annotation starting with “Valid”. The actual validation is delegated to WebDataBinder.validate() , which ultimately invokes Hibernate Validator. Method‑level validation uses AOP via MethodValidationPostProcessor and MethodValidationInterceptor , also delegating to Hibernate Validator for parameter and return‑value checks.
Author: 夜尽天明_ (source: juejin.cn/post/6856541106626363399)
Java Tech Enthusiast
Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!
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.