Spring Validation in Java
Introduction
In any robust application, never trust user input. Invalid data can lead to security vulnerabilities, application crashes, and corrupted databases. Spring Validation provides a standardized, declarative way to validate incoming data before it even reaches your business logic.
Built on top of the Java Bean Validation API (JSR 380) and typically powered by Hibernate Validator under the hood, Spring Validation allows you to enforce complex data rules using simple annotations.
Core Constraints
Spring Validation relies on standard annotations placed on fields within your DTOs (Data Transfer Objects) or entities.
Common constraints include:
@NotNull: The value cannot be null.@NotBlank: The string cannot be null and must contain at least one non-whitespace character.@Size(min = 2, max = 50): Validates the length of strings or the size of collections.@Email: Ensures the string is a valid email format.@Min/@Max: Validates numeric boundaries.@Pattern: Validates against a regular expression.
Code Examples
1. Validating a DTO
First, define your constraints on the DTO class.
import jakarta.validation.constraints.*;
public class UserRegistrationDto {
@NotBlank(message = "Username is required")
@Size(min = 3, max = 20, message = "Username must be between 3 and 20 characters")
private String username;
@NotBlank(message = "Email is required")
@Email(message = "Must be a valid email format")
private String email;
@Min(value = 18, message = "Age must be at least 18")
private int age;
// Getters and Setters...
}
Next, trigger the validation in your Controller using the @Valid or @Validated annotation.
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping("/register")
public ResponseEntity<String> registerUser(@Valid @RequestBody UserRegistrationDto dto) {
// If the code reaches here, the DTO is guaranteed to be valid.
return ResponseEntity.ok("User registered successfully");
}
}
2. Handling Validation Errors Gracefully
When validation fails, Spring throws a MethodArgumentNotValidException. By default, this returns a generic 400 Bad Request error. We can use @ControllerAdvice to intercept this and return a structured JSON response indicating exactly which fields failed.
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.FieldError;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class ValidationExceptionHandler {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public Map<String, String> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return errors; // Returns e.g., {"username": "Username is required", "age": "Age must be at least 18"}
}
}
3. Creating Custom Validators
Sometimes built-in annotations aren't enough. You can create your own custom annotation and validation logic.
Step A: Define the Annotation
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ValidSkuValidator.class)
public @interface ValidSku {
String message() default "Invalid SKU format. Must start with 'SKU-'";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Step B: Implement the Validator
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class ValidSkuValidator implements ConstraintValidator<ValidSku, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return false;
}
return value.startsWith("SKU-");
}
}
You can now use @ValidSku on any DTO field just like standard annotations.
Summary
- Declarative Approach: Spring Validation allows you to enforce business rules without cluttering your logic with
if/elsechecks. - Fail Fast: Use
@Validat the controller layer to intercept bad data before it reaches the service layer. - Customizable: Global exception handlers allow you to map validation errors to clean API responses, and custom validators extend functionality for complex domain rules.