Backend Development 17 min read

Comprehensive Guide to Spring Validation: Usage, Advanced Features, and Implementation Principles

This article provides a thorough tutorial on Spring Validation, covering basic usage with requestBody and requestParam, dependency setup, group, nested, collection and custom validations, programmatic validation, fail‑fast mode, differences between @Valid and @Validated, as well as the underlying implementation mechanisms in Spring MVC and method‑level AOP validation.

Top Architect
Top Architect
Top Architect
Comprehensive Guide to Spring Validation: Usage, Advanced Features, and Implementation Principles

Spring Validation is a wrapper around Hibernate Validator that enables automatic parameter validation in Spring MVC applications. It supports both simple and complex validation scenarios, including requestBody objects, request parameters, group validation, nested objects, collections, custom constraints, and programmatic validation.

Simple Usage

The JSR‑303 validation-api defines the Bean Validation standard, while Hibernate Validator provides the implementation with annotations such as @Email and @Length . In a Spring Boot project, adding the hibernate-validator dependency (automatically included for Spring Boot < 2.3.x) enables Spring Validation.

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

For POST/PUT requests, DTO objects are annotated with @Validated (or @Valid ) to trigger validation. Example:

@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 executes only after successful validation
    return Result.ok();
}

GET requests can validate @RequestParam or @PathVariable parameters directly on method arguments, requiring the controller class to be annotated with @Validated :

@RestControllerAdvice
public class CommonExceptionHandler {
    @ExceptionHandler({MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.OK)
    public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        // build error message from BindingResult
        return Result.fail(BusinessCode.PARAMETER_VALIDATION_FAILED, msg);
    }
    @ExceptionHandler({ConstraintViolationException.class})
    @ResponseStatus(HttpStatus.OK)
    public Result handleConstraintViolationException(ConstraintViolationException ex) {
        return Result.fail(BusinessCode.PARAMETER_VALIDATION_FAILED, ex.getMessage());
    }
}

Advanced Usage

Group Validation

Define validation groups to apply different constraints in different contexts (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 {}
}

Apply 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

When a DTO contains another object, annotate the field with @Valid to cascade validation:

@Data
public class UserDTO {
    // ... other fields ...
    @NotNull(groups = {Save.class, Update.class})
    @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 JSON array, wrap the collection in a custom class and annotate it with @Valid :

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

Create a custom annotation and validator, e.g., 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 context) {
        if (value != null) {
            return PATTERN.matcher(value).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
> 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

The two annotations are interchangeable for triggering validation; @Validated also supports validation groups.

Implementation Principles

requestBody Validation Mechanism

Spring MVC’s RequestResponseBodyMethodProcessor reads the request body, creates the DTO, and then calls validateIfApplicable() , which looks for @Validated or any annotation starting with "Valid" and invokes WebDataBinder.validate() . The binder delegates to Hibernate Validator.

public Object resolveArgument(MethodParameter parameter, ... ) {
    Object arg = readWithMessageConverters(...);
    if (binderFactory != null) {
        WebDataBinder binder = binderFactory.createBinder(...);
        if (arg != null) {
            validateIfApplicable(binder, parameter);
            if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(...)) {
                throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
            }
        }
        return adaptArgumentIfNecessary(arg, parameter);
    }
}

Method‑Level Validation Mechanism

Spring registers a MethodValidationPostProcessor that creates an AOP advisor for beans annotated with @Validated . The advisor uses MethodValidationInterceptor to invoke ExecutableValidator.validateParameters() and validateReturnValue() , which again delegate to Hibernate Validator.

public Object invoke(MethodInvocation invocation) throws Throwable {
    Class
[] groups = determineValidationGroups(invocation);
    ExecutableValidator execVal = this.validator.forExecutables();
    Set
> result = execVal.validateParameters(
        invocation.getThis(), invocation.getMethod(), invocation.getArguments(), groups);
    if (!result.isEmpty()) {
        throw new ConstraintViolationException(result);
    }
    Object returnValue = invocation.proceed();
    result = execVal.validateReturnValue(invocation.getThis(), invocation.getMethod(), returnValue, groups);
    if (!result.isEmpty()) {
        throw new ConstraintViolationException(result);
    }
    return returnValue;
}

Thus, regardless of whether validation is performed on request bodies, method parameters, or return values, the core validation work is always carried out by Hibernate Validator, with Spring providing convenient integration and error handling.

backendJavaSpringvalidationSpring BootAPIHibernate 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.