Backend Development 14 min read

Mastering Spring Boot Validation with Hibernate Validator: An End‑to‑End Guide

This article explains why back‑end parameter validation is essential, introduces the JSR‑303 Bean Validation specification and Hibernate Validator, demonstrates common constraint annotations, shows how to integrate validation in Spring Boot, create custom annotations, and resolve typical pitfalls.

macrozheng
macrozheng
macrozheng
Mastering Spring Boot Validation with Hibernate Validator: An End‑to‑End Guide

1. Introduction

Because network transmission is unreliable and front‑end data can be tampered, back‑end parameter validation is essential; applications must ensure incoming data is semantically correct.

2. Pain points of data validation

To guarantee correct data semantics, many checks are required, leading to duplicated validation across layers, resulting in business‑unrelated code, poor maintainability, and increased developer workload.

3. JSR‑303 validation specification and implementation

Binding validation logic to domain models is necessary, which led to the JSR‑303 Bean Validation specification. Hibernate Validator is the reference implementation, providing all constraints defined by JSR‑303 and additional extensions.

Common constraint annotations provided by Hibernate Validator

@Null

: element must be null

@NotNull

: element must not be null

@AssertTrue

: element must be true

@AssertFalse

: element must be false

@Min(value)

: numeric value must be greater than or equal to the specified minimum

@Max(value)

: numeric value must be less than or equal to the specified maximum

@DecimalMin(value)

: numeric value must be greater than or equal to the specified minimum

@DecimalMax(value)

: numeric value must be less than or equal to the specified maximum

@Size(min, max)

: size must be within the specified range

@Digits(integer, fraction)

: numeric value must have an acceptable number of integer and fraction digits

@Past

: element must be a past date

@Future

: element must be a future date

@Pattern(value)

: element must match the given regular expression

@Email

: element must be a valid email address

@Length

: string length must be within the specified range

@NotEmpty

: string or collection must not be empty

@Range

: numeric value must be within the specified range

4. Using validation annotations

In Spring Boot, adding the

spring-boot-starter-validation

starter makes Hibernate Validator easy to use.

<code>&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-validation&lt;/artifactId&gt;
&lt;/dependency&gt;</code>

Two ways to enable validation: implement the

Validator

interface or use constraint annotations. Annotations are recommended for most cases.

4.1 Basic usage of constraint annotations

Annotate method parameters, for example:

<code>@Data
public class Student {
    @NotBlank(message = "Name is required")
    private String name;
    @NotNull(message = "Age is required")
    @Range(min = 1, max = 50, message = "Age must be between 1 and 50")
    private Integer age;
    @NotEmpty(message = "Scores are required")
    private List&lt;Double&gt; scores;
}</code>

Define a POST endpoint:

<code>@RestController
@RequestMapping("/student")
public class StudentController {
    @PostMapping("/add")
    public Rest<?> addStudent(@Valid @RequestBody Student student) {
        return RestBody.okData(student);
    }
}</code>

When the request contains invalid data, a

MethodArgumentNotValidException

is thrown, e.g., age out of range.

<code>POST /student/add HTTP/1.1
Host: localhost:8888
Content-Type: application/json

{
    "name": "felord.cn",
    "age": 77,
    "scores": [55]
}</code>

GET request

Similarly, a GET endpoint:

<code>@GetMapping("/get")
public Rest<?> getStudent(@Valid Student student) {
    return RestBody.okData(student);
}</code>

Invalid parameters trigger a

BindException

instead of

MethodArgumentNotValidException

because

@RequestBody

is not used.

<code>GET /student/get?name=felord.cn&age=12 HTTP/1.1
Host: localhost:8888</code>

Custom annotation

When a single annotation cannot cover multiple constraints, a composed annotation can be created. Example combining

@NotNull

and

@Range

into

@Age

:

<code>import org.hibernate.validator.constraints.Range;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.NotNull;
import java.lang.annotation.*;

@Constraint(validatedBy = {})
@SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,
        ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@NotNull
@Range(min = 1, max = 50)
@Documented
@ReportAsSingleViolation
public @interface Age {
    String message() default "Age is required and must be between 1-50";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}</code>

For enum‑based validation, define an enum and a corresponding

ConstraintValidator

:

<code>public enum Colors {
    RED, YELLOW, BLUE
}</code>
<code>public class ColorConstraintValidator implements ConstraintValidator<Color, String> {
    private static final Set<String> COLOR_CONSTRAINTS = new HashSet<>();

    @Override
    public void initialize(Color constraintAnnotation) {
        Colors[] value = constraintAnnotation.value();
        List<String> list = Arrays.stream(value)
                .map(Enum::name)
                .collect(Collectors.toList());
        COLOR_CONSTRAINTS.addAll(list);
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return COLOR_CONSTRAINTS.contains(value);
    }
}</code>
<code>@Constraint(validatedBy = ColorConstraintValidator.class)
@Documented
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,
        ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Color {
    String message() default "Invalid color";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    Colors[] value();
}</code>

Apply it to a parameter wrapper:

<code>@Data
public class Param {
    @Color({Colors.BLUE, Colors.YELLOW})
    private String color;
}</code>

Calling

/student/color?color=CAY

throws

BindException

; using

BLUE

or

YELLOW

succeeds.

4.2 Common issues

Basic type validation not effective

Validating a simple method argument with

@Valid @Color

does not work in Spring Boot 2.3.1.RELEASE unless the controller class is annotated with

@Validated

, which then causes a

ConstraintViolationException

.

Collection element validation

When a method parameter is a collection, adding

@Validated

to the class enables validation of each element; otherwise a

ConstraintViolationException

is thrown.

Nested validation not working

To validate nested objects (e.g., a

School

field inside

Student

), the nested field must be annotated with

@Valid

in addition to other constraints. POST requests require this annotation, while GET requests validate automatically.

Each level of nesting requires an additional @Valid . Typically @NotNull , @NotEmpty and @Valid work together to achieve validation.

5. Summary

Using a validation framework lets developers focus on business logic. This article reviewed the usage of Hibernate Validator, common pitfalls, and how to handle validation exceptions with Spring Boot's unified exception handling.

JSR-303Spring BootCustom AnnotationHibernate Validatordata validation
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

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.