Backend Development 16 min read

Comprehensive Guide to Spring Validation: Best Practices, Advanced Features, and Implementation Details

This article provides an in‑depth tutorial on Spring Validation, covering basic usage, dependency configuration, requestBody and requestParam validation, unified exception handling, advanced techniques such as group, nested, and collection validation, custom validators, programmatic validation, fail‑fast mode, and the underlying implementation mechanisms.

Top Architect
Top Architect
Top Architect
Comprehensive Guide to Spring Validation: Best Practices, Advanced Features, and Implementation Details

Spring Validation is a powerful extension of Hibernate Validator that enables automatic parameter validation for Spring MVC controllers. It follows the JSR‑303 Bean Validation specification and adds convenient annotations like @Email and @Length for common constraints.

Simple Usage

When using Spring Boot version < 2.3.x, the spring-boot-starter-web starter automatically includes the hibernate-validator dependency. For newer versions, add the dependency manually:

<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-validator</artifactId>
  <version>6.0.1.Final</version>
</dependency>

For web services, validate request parameters in the controller layer. POST and PUT requests typically use @RequestBody with a DTO object annotated with @Validated . Example:

@PostMapping("/save")
public Result saveUser(@RequestBody @Validated UserDTO userDTO) {
    return Result.ok();
}

If validation fails, Spring throws MethodArgumentNotValidException and returns a 400 Bad Request response.

Method Parameter Validation

For GET requests, use @RequestParam or @PathVariable . Annotate the controller class with @Validated and place constraint annotations on method parameters, e.g., @Min :

@GetMapping("{userId}")
public Result detail(@PathVariable("userId") @Min(10000000000000000L) Long userId) {
    // ...
    return Result.ok(userDTO);
}

Unified Exception Handling

Handle validation exceptions globally to return a consistent response, always using 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("校验失败:");
        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 constraints in save vs. update scenarios.

@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 {}
}

Apply groups in controller methods:

@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 : Use @Valid on nested DTO fields to cascade validation.

@Data
public class UserDTO {
    @NotNull(groups = {Save.class, Update.class})
    @Length(min = 2, max = 10, groups = {Save.class, Update.class})
    private String userName;
    @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;
    }
}

Collection Validation : Wrap a List with @Valid to validate each element.

public class ValidationList
implements List
{
    @Delegate
    @Valid
    public List
list = new ArrayList<>();
    @Override
    public String toString() { return list.toString(); }
}

Controller method example:

@PostMapping("/saveList")
public Result saveList(@RequestBody @Validated(UserDTO.Save.class) ValidationList
userList) {
    return Result.ok();
}

Custom Validation : Define a custom annotation and validator, e.g., @EncryptId to validate encrypted IDs.

@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 validate manually.

@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 at the first violation.

@Bean
public Validator validator() {
    ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
        .configure()
        .failFast(true)
        .buildValidatorFactory();
    return factory.getValidator();
}

Implementation Principles

For requestBody validation, Spring MVC’s RequestResponseBodyMethodProcessor calls validateIfApplicable() , which triggers WebDataBinder.validate() . This ultimately delegates to Hibernate Validator’s validate() method.

Method‑level validation uses AOP. MethodValidationPostProcessor registers a pointcut for methods annotated with @Validated or @Valid , and applies MethodValidationInterceptor . The interceptor invokes ExecutableValidator.validateParameters() and validateReturnValue() , again delegating to Hibernate Validator.

Thus, both request body and method‑level validations share the same underlying validation engine.

backendJavaDTOSpringValidationSpring BootHibernate Validator
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.