Spring Boot Request Validation
1. Add the validation dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Spring Boot uses Jakarta Bean Validation, commonly implemented by Hibernate Validator. The validator checks annotations and reports violations before the controller method runs.
2. Common validation annotations
| Annotation | Checks | Example |
|---|---|---|
@NotNull |
Value is not null | @NotNull Long departmentId |
@NotEmpty |
Text or collection is not null or empty | @NotEmpty List<String> roles |
@NotBlank |
Text contains non-whitespace characters | @NotBlank String name |
@Size |
Text or collection length | @Size(min = 8, max = 100) |
@Email |
Email-like format | @Email String email |
@Min / @Max |
Integer or long range | @Min(18) int age |
@DecimalMin / @DecimalMax |
Decimal range | @DecimalMin("0.01") BigDecimal price |
@Positive / @PositiveOrZero |
Number is greater than zero | @Positive BigDecimal amount |
@Negative / @NegativeOrZero |
Number is below zero | @Negative int adjustment |
@Past / @PastOrPresent |
Date is before today | @Past LocalDate birthDate |
@Future / @FutureOrPresent |
Date is after today | @Future LocalDate deliveryDate |
@Pattern |
Matches a regular expression | @Pattern(regexp = "[A-Z]{2}[0-9]{4}") |
@AssertTrue |
Boolean must be true | @AssertTrue boolean acceptedTerms |
@NotNull allows an empty string, while
@NotBlank rejects empty and whitespace-only text. Numeric
constraints do not reject null; combine them with
@NotNull when the number is required.
3. Validate a REST request DTO
A DTO is the safest place for request validation. It describes what the API accepts without exposing database fields such as internal IDs, audit columns, or password hashes.
public record RegisterRequest(
@NotBlank(message = "Name is required")
@Size(max = 80, message = "Name is too long")
String name,
@NotBlank(message = "Email is required")
@Email(message = "Enter a valid email address")
String email,
@NotBlank(message = "Password is required")
@Size(min = 8, max = 100,
message = "Password must contain 8 to 100 characters")
String password,
@NotNull(message = "Age is required")
@Min(value = 18, message = "You must be at least 18")
Integer age) {
}
4. Use @Valid in the controller
@PostMapping("/users")
public ResponseEntity<UserResponse> register(
@Valid @RequestBody RegisterRequest request) {
UserResponse user = userService.register(request);
URI location = URI.create("/api/users/" + user.id());
return ResponseEntity.created(location).body(user);
}
If the request is invalid, Spring throws
MethodArgumentNotValidException and the service method is
not called. A global exception handler should convert that exception to
a friendly 400 Bad Request response.
5. Validate path and query parameters
Use @Validated on the controller or service class when
validating method parameters such as IDs, page sizes, and query values.
@RestController
@Validated
@RequestMapping("/api/products")
public class ProductController {
@GetMapping("/{id}")
public Product findById(
@PathVariable
@Positive(message = "ID must be positive") Long id) {
return service.findById(id);
}
@GetMapping
public Page<Product> search(
@RequestParam(defaultValue = "0")
@PositiveOrZero int page,
@RequestParam(defaultValue = "20")
@Min(1) @Max(100) int size) {
return service.search(page, size);
}
}
Depending on the Spring version, invalid method parameters may produce
ConstraintViolationException or
HandlerMethodValidationException. Handle both in your
global exception handler when supporting multiple Spring Boot versions.
6. Validate nested objects and collections
Add @Valid to a nested field or collection so validation
continues into its child objects. Without it, the child annotations are
not evaluated.
public record CreateOrderRequest(
@NotEmpty List<@Valid OrderItemRequest> items,
@Valid ShippingAddress address) {
}
public record OrderItemRequest(
@NotNull Long productId,
@Min(1) @Max(100) int quantity) {
}
public record ShippingAddress(
@NotBlank String street,
@NotBlank String city,
@Pattern(regexp = "[0-9]{5,6}") String postalCode) {
}
7. Validate entity fields
Entity validation protects data when JPA persists or updates an entity. It is useful as a second line of defense, but it should not replace DTO validation because entities can be used in different workflows.
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
@Column(nullable = false)
private String name;
@NotNull
@Positive
@Column(nullable = false, precision = 12, scale = 2)
private BigDecimal price;
@PositiveOrZero
@Column(nullable = false)
private int stock;
}
@Column(nullable = false) creates a database-level null
rule, while @NotNull reports a validation error before the
database call. Use both when the rule is important.
8. Validate business rules in the service
Bean Validation checks individual values. Business validation checks rules that require application data, such as duplicate email, stock availability, or whether a user is allowed to update an order.
@Service
public class OrderService {
public Order placeOrder(CreateOrderRequest request) {
for (OrderItemRequest item : request.items()) {
Product product = productRepository.findById(item.productId())
.orElseThrow(() -> new ProductNotFoundException(
item.productId()));
if (product.getStock() < item.quantity()) {
throw new InsufficientStockException(product.getName());
}
}
return saveOrder(request);
}
}
9. Cross-field validation
Some rules compare two or more fields, such as password confirmation or a start date before an end date. Create a class-level constraint for these rules instead of forcing the controller to compare fields.
@PasswordsMatch
public record ChangePasswordRequest(
@NotBlank String newPassword,
@NotBlank String confirmPassword) {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordsMatchValidator.class)
public @interface PasswordsMatch {
String message() default "Passwords do not match";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class PasswordsMatchValidator
implements ConstraintValidator<PasswordsMatch, ChangePasswordRequest> {
@Override
public boolean isValid(ChangePasswordRequest value,
ConstraintValidatorContext context) {
return value == null
|| Objects.equals(value.newPassword(),
value.confirmPassword());
}
}
10. Custom field validator
Use a custom annotation when a repeated field rule is not provided by the standard constraints, such as an employee code format or a supported country code.
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EmployeeCodeValidator.class)
public @interface ValidEmployeeCode {
String message() default "Invalid employee code";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class EmployeeCodeValidator
implements ConstraintValidator<ValidEmployeeCode, String> {
@Override
public boolean isValid(String value,
ConstraintValidatorContext context) {
return value != null && value.matches("EMP-[0-9]{5}");
}
}
11. Validate service method parameters
Use @Validated on a Spring service when method arguments
must be checked even when the method is called outside a REST
controller.
@Service
@Validated
public class PaymentService {
public Payment findPayment(
@NotNull @Positive Long paymentId) {
return repository.findById(paymentId);
}
}
12. Database validation and uniqueness
A validation annotation cannot safely check whether an email is unique
because another request may insert the same email at the same time. Use
both an application check and a database unique constraint, then map the
database conflict to 409 Conflict.
@Column(nullable = false, unique = true)
private String email;
if (userRepository.existsByEmail(request.email())) {
throw new DuplicateEmailException(request.email());
}
// Also handle DataIntegrityViolationException globally.
13. Return useful validation errors
A frontend needs the field name and a human-readable message. Do not return a stack trace or internal class name.
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ValidationError> validationErrors(
MethodArgumentNotValidException ex) {
Map<String, String> fields = new LinkedHashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
fields.put(error.getField(), error.getDefaultMessage()));
return ResponseEntity.badRequest().body(
new ValidationError(400, "Validation failed", fields));
}
public record ValidationError(
int status, String error, Map<String, String> fields) {
}
{
"status": 400,
"error": "Validation failed",
"fields": {
"email": "Enter a valid email address",
"password": "Password must contain 8 to 100 characters"
}
}
14. End-to-end registration example
- The client sends JSON to
POST /api/users. -
@RequestBodyconverts JSON toRegisterRequest. -
@Validchecks required fields, email, age, and password. - If valid, the service checks whether the email already exists.
- The service hashes the password and saves the user through JPA.
-
The API returns
201 Created; invalid input returns400.
POST /api/users
Content-Type: application/json
{
"name": "Anand Kumar",
"email": "anand@example.com",
"password": "StrongPassword123!",
"age": 28
}
15. Validation best practices
- Validate at the API boundary with request DTOs.
-
Use
@Validfor request bodies and@Validatedfor method parameters. -
Use
@Validon nested objects and collection elements. - Keep business rules in services, not only in annotations.
- Use database constraints for uniqueness and final data integrity.
- Return field-level errors with a consistent response format.
- Never trust frontend validation; validate again on the server.
- Do not return passwords, tokens, or sensitive values in errors or logs.
Spring Boot Request Validation FAQs
What is the difference between @Valid and @Validated?
@Valid triggers standard Bean Validation, while @Validated also supports validation groups and method-level validation.
Where should business validation happen?
Business validation belongs in the service layer because it may require database access or coordination between multiple fields and records.
Can validation guarantee unique values?
No. Check for a friendly error in the service, but enforce uniqueness with a database constraint to handle concurrent requests safely.
Next: Global Exception HandlingContinue learning
