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.
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-validationstarter makes Hibernate Validator easy to use.
<code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency></code>Two ways to enable validation: implement the
Validatorinterface 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<Double> 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
MethodArgumentNotValidExceptionis 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
BindExceptioninstead of
MethodArgumentNotValidExceptionbecause
@RequestBodyis 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
@NotNulland
@Rangeinto
@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=CAYthrows
BindException; using
BLUEor
YELLOWsucceeds.
4.2 Common issues
Basic type validation not effective
Validating a simple method argument with
@Valid @Colordoes 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
@Validatedto the class enables validation of each element; otherwise a
ConstraintViolationExceptionis thrown.
Nested validation not working
To validate nested objects (e.g., a
Schoolfield inside
Student), the nested field must be annotated with
@Validin 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.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.