Mastering Spring Boot 3 Validation: From Annotations to Global Error Handling
This article provides a comprehensive guide to data validation in Spring Boot 3, covering Bean Validation annotations, controller and service‑layer checks, JPA entity validation, custom constraints, validation groups, programmatic validation, and internationalized error messages with practical code examples and tests.
1. Introduction
Ensuring that incoming data conforms to expected formats and rules is essential for robust and secure Spring Boot applications. Spring Boot leverages the Bean Validation API (JSR‑380) and Hibernate Validator to simplify this process.
2. Practical Cases
2.1 Basic Annotations
Common validation annotations include @NotNull , @Size , @Min , @Max , @NotEmpty , @NotBlank , and @Pattern . Add the starter dependency to enable Hibernate Validator:
<code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency></code>2.2 Controller Parameter Validation
Three kinds of parameters can be validated:
Request Body : use @Valid or @Validated on the method argument.
Path Variables : annotate the variable with validation constraints (e.g., @Min(5) ).
Query Parameters : apply constraints directly on the method parameter.
Example of a validated request body:
<code>public class Customer {
@Email
private String email;
@NotBlank
private String name;
// other fields
}</code>Controller method:
<code>@RestController
public class ValidateRequestBodyController {
@PostMapping("/body")
public ResponseEntity<String> body(@Valid @RequestBody Input input) {
return ResponseEntity.ok("valid");
}
}</code>Unit test demonstrates a 400 response when validation fails.
2.3 Service‑Layer Validation
Apply @Validated at the class level and @Valid on method parameters to validate non‑controller beans:
<code>@Service
@Validated
public class ValidatingService {
public void validateInput(@Valid Input input) {
// business logic
}
}</code>2.4 JPA Entity Validation
Hibernate validates entity fields during persistence. Example entity:
<code>@Entity
public class Dog {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotEmpty
private String name;
}</code>Saving an invalid entity throws ConstraintViolationException . Validation can be disabled via:
<code>spring:
jpa:
properties:
'[javax.persistence.validation.mode]': none</code>2.5 Custom Validation Annotations
Define a constraint annotation with @Constraint and implement ConstraintValidator :
<code>@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PrefixConstraintValidator.class)
public @interface PrefixConstraint {
String value() default "";
String message() default "{validator.prefix.error}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}</code>Validator implementation:
<code>public class PrefixConstraintValidator implements ConstraintValidator<PrefixConstraint, CharSequence> {
private String prefix;
@Override
public void initialize(PrefixConstraint pc) { prefix = pc.value(); }
@Override
public boolean isValid(CharSequence value, ConstraintValidatorContext ctx) {
return value != null && value.toString().startsWith(prefix);
}
}</code>2.6 Programmatic Validation
Use the Bean Validation API directly when you need explicit validation:
<code>ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Dog>> violations = validator.validate(new Dog());
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}</code>2.7 Validation Groups
Define marker interfaces (e.g., G1 , G2 ) and assign them to constraints to apply different rules in different scenarios:
<code>public interface G1 {}
public interface G2 {}
public class User {
@Min(1) @Max(150)
private Integer age;
@NotEmpty(groups = G1.class)
private String name;
@NotEmpty(groups = G2.class)
private String address;
}</code>Controller methods can trigger specific groups with @Validated(G1.class) or @Validated(G2.class) .
2.8 Validation Error Handling
Two approaches to return meaningful error information:
Inject BindingResult after the validated object and build a custom response.
Define a global @RestControllerAdvice that catches MethodArgumentNotValidException or ConstraintViolationException and formats the errors.
<code>@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public R globalException(Exception ex) {
if (ex instanceof MethodArgumentNotValidException e) {
List<String> errs = e.getFieldErrors().stream()
.map(err -> err.getField() + ", " + err.getDefaultMessage())
.collect(Collectors.toList());
return R.error(errs);
}
return R.error("Unexpected error");
}
}</code>2.9 Internationalized Error Messages
Specify messages directly with message="..." or use placeholders that refer to keys in messages_zh_CN.properties and messages_en_US.properties . Spring loads these files via the spring.messages.basename property.
<code>@NotEmpty(message = "{name.empty.error}")
private String name;
</code>2.10 Custom Validation Annotation Usage
Annotations whose name starts with Valid (or are meta‑annotated with @Validated ) are automatically processed by Spring MVC. Example:
<code>@Retention(RUNTIME)
@Target(PARAMETER)
public @interface ValidPack {}
@PostMapping("/save")
public R save(@RequestBody @ValidPack User user) {
return R.success(user);
}</code>The article concludes with a reminder to like, share, and collect the content.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.