Master Spring Validation: Best Practices and Advanced Techniques
This article provides a comprehensive guide to Spring Validation, covering basic usage, dependency setup, requestBody and requestParam validation, group and nested validation, collection checks, custom constraints, programmatic validation, fail‑fast configuration, and the underlying implementation mechanisms within Spring MVC and Hibernate Validator.
Previously I wrote an article about Spring Validation, but it felt superficial; this article aims to fully master Spring Validation by presenting best practices and the underlying implementation principles across various scenarios.
Simple Usage
Import Dependencies
If the Spring Boot version is lower than 2.3.x, spring-boot-starter-web automatically includes hibernate-validator. For versions higher than 2.3.x, 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, use @RequestBody with a DTO object. Annotate the DTO with @Validated (or @Valid) to trigger automatic validation. Example DTO:
@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;
}Controller method:
@PostMapping("/save")
public Result saveUser(@RequestBody @Validated UserDTO userDTO) {
// business logic executes only after validation passes
return Result.ok();
}requestParam/PathVariable Parameter Validation
For GET requests, use @RequestParam or @PathVariable. Place constraint annotations on method parameters or on a DTO. Example:
@RestController
@Validated
public class UserController {
@GetMapping("{userId}")
public Result detail(@PathVariable("userId") @Min(10000000000000000L) Long userId) {
// logic after validation
return Result.ok();
}
@GetMapping("getByAccount")
public Result getByAccount(@Length(min = 6, max = 20) @NotNull String account) {
// logic after validation
return Result.ok();
}
}Advanced Usage
Group Validation
Define validation groups to apply different rules in different contexts (e.g., save vs. update). Declare 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 ...
public interface Save {}
public interface Update {}
}Controller usage:
@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 to cascade validation:
@Data
public class UserDTO {
// ... other fields ...
@NotNull @Valid
private Job job;
@Data
public static class Job {
@Min(1)
private Long jobId;
@NotNull @Length(min = 2, max = 10)
private String jobName;
@NotNull @Length(min = 2, max = 10)
private String position;
}
}Collection Validation
To validate each element of a collection, wrap the collection in a custom class and annotate the list with @Valid:
public class ValidationList<E> implements List<E> {
@Delegate
@Valid
public List<E> list = new ArrayList<>();
@Override
public String toString() { return list.toString(); }
}Controller example:
@PostMapping("/saveList")
public Result saveList(@RequestBody @Validated(UserDTO.Save.class) ValidationList<UserDTO> userList) {
return Result.ok();
}Custom Validation
Create a custom constraint annotation and its validator. Example for an encrypted ID:
@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<? extends Payload>[] payload() default {};
} public class EncryptIdValidator implements ConstraintValidator<EncryptId, String> {
private static final Pattern PATTERN = Pattern.compile("^[a-f\\d]{32,256}$");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) return true;
return PATTERN.matcher(value).matches();
}
}Programmatic Validation
Inject javax.validation.Validator and invoke it manually:
@Autowired
private javax.validation.Validator globalValidator;
@PostMapping("/saveWithCodingValidate")
public Result saveWithCodingValidate(@RequestBody UserDTO userDTO) {
Set<ConstraintViolation<UserDTO>> violations = globalValidator.validate(userDTO, UserDTO.Save.class);
if (!violations.isEmpty()) {
// handle violations
}
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
@Valid
@Validated
Provider
JSR‑303
Spring
Supports Groups
No
Yes
Supported Targets
METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE
TYPE, METHOD, PARAMETER
Nested Validation
Supported
Not Supported
Implementation Principles
requestBody Validation Mechanism
Spring MVC uses RequestResponseBodyMethodProcessor to read the request body, bind it to a DTO, and then invoke validateIfApplicable, which checks for @Validated or any annotation starting with "Valid" and calls WebDataBinder.validate(). The binder delegates to Hibernate Validator for actual constraint checking.
Method‑Level Validation Mechanism
Spring registers an AOP advisor for beans annotated with @Validated. The advisor applies MethodValidationInterceptor, which uses ExecutableValidator from Hibernate Validator to validate method parameters and return values before and after method execution.
Underlying Hibernate Validator
Both requestBody validation and method‑level validation ultimately rely on Hibernate Validator to evaluate the constraint annotations defined on beans, method parameters, and return values.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
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.
