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.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Master Spring Validation: From Basic to Advanced Parameter Checks

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.

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.

BackendjavaspringvalidationHibernate ValidatorParameter Validation
Su San Talks Tech
Written by

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.

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.