Master Spring Validation: From Basic to Advanced Parameter Checks
This article explains how to use Spring Validation for automatic request parameter checks in Java web applications, covering basic setup, DTO annotations, requestBody and requestParam validation, unified exception handling, and advanced features like group, nested, collection, custom, and fail‑fast validation.
Simple Usage
The Java API (JSR303) defines the Bean Validation standard via validation-api, while hibernate-validator provides the implementation and adds annotations such as @Email and @Length. Spring Validation wraps Hibernate Validator to support Spring MVC automatic validation. The following example uses a Spring Boot project.
Adding 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>In web services, controller layers must validate parameters to prevent illegal input from affecting business logic. Request parameters are typically of two forms: POST or PUT requests use requestBody to pass parameters. GET requests use requestParam or PathVariable to pass parameters.
Both requestBody and method‑level validation ultimately invoke Hibernate Validator; Spring Validation adds a thin wrapper.
requestBody Parameter Validation
For POST / PUT requests, define a DTO and annotate it with @Validated. Example: a User saving API requires userName length 2‑10, and account and password length 6‑20. Validation failures throw MethodArgumentNotValidException, which Spring converts to a 400 Bad Request.
@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;
}Controller method:
@PostMapping("/save")
public Result saveUser(@RequestBody @Validated UserDTO userDTO) {
// business logic executes only after successful validation
return Result.ok();
}requestParam/PathVariable Parameter Validation
For GET requests, you can place parameters directly in method arguments and annotate them, or use a DTO when there are many parameters. The controller class must be annotated with @Validated and each parameter with constraint annotations such as @Min or @Length. Validation failures throw 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();
userDTO.setAccount(account);
// ... set other fields
return Result.ok(userDTO);
}
}Unified Exception Handling
When validation fails, MethodArgumentNotValidException or ConstraintViolationException is thrown. A global exception handler can convert these to a consistent response format, always returning HTTP 200 with 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
Group Validation
When the same DTO is used in different scenarios with different validation rules, define groups and specify them in @Validated. Example: UserId is optional when saving but must be ≥10000000000000000L when updating.
@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 similar group annotations
public interface Save {}
public interface Update {}
}Controller methods specify the group:
@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 field is another object, annotate the field with @Valid to trigger nested validation. Example: UserDTO contains a Job 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
}
}Collection Validation
When receiving a JSON array, wrap a List in a custom class annotated with @Valid to enable validation of each element.
public class ValidationList<E> implements List<E> {
@Delegate
@Valid
public List<E> list = new ArrayList<>();
@Override
public String toString() {
return list.toString();
}
// other List methods delegated to 'list'
}Controller method:
@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: an @EncryptId annotation validates that a string consists of 32‑256 characters of digits or a‑f.
@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<? 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) {
Matcher matcher = PATTERN.matcher(value);
return matcher.find();
}
return true;
}
}Programmatic Validation
Inject javax.validation.Validator and call its API directly.
@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()) {
for (ConstraintViolation<UserDTO> v : violations) {
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 validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}Validator Implementation Principles
requestBody Validation Mechanism
Spring MVC's RequestResponseBodyMethodProcessor resolves method arguments, then calls validateIfApplicable, which invokes the underlying WebDataBinder.validate. This ultimately delegates to Hibernate Validator.
Method‑Level Validation Mechanism
Spring registers a MethodValidationPostProcessor that creates an AOP advisor for beans annotated with @Validated. The advisor uses MethodValidationInterceptor to validate method parameters and return values via Hibernate Validator before proceeding.
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.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.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.
