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.

Programmer DD
Programmer DD
Programmer DD
Master Spring Validation: Best Practices and Advanced Techniques

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendspringvalidationHibernate
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

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.