Why this matters
- Validation at the API boundary rejects bad data before it touches business logic or the database.
- Built-in constraints cover 90% of BookStore needs; custom validators handle domain rules like ISBN format.
- Consistent validation error responses (via
@ControllerAdvice) improve client developer experience.
Enable validation
Add the validation starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Annotate the controller parameter:
@PostMapping
public BookResponse create(@RequestBody @Valid CreateBookRequest request) {
return BookResponse.from(bookService.create(request));
}
@Valid triggers validation before the method body executes.
Built-in constraints on DTOs
public record CreateBookRequest(
@NotBlank(message = "Title is required")
@Size(max = 255, message = "Title must be at most 255 characters")
String title,
@NotBlank
@Pattern(regexp = "\\d{13}", message = "ISBN must be exactly 13 digits")
String isbn,
@NotNull
@Positive(message = "Price must be positive")
@DecimalMax(value = "99999.99", message = "Price exceeds maximum")
BigDecimal price,
@NotBlank
@Size(min = 2, max = 50)
String genre
) {}
Frequently used constraints
@NotNull— Field must be present (but empty string passes).@NotBlank— Not null, not empty, not whitespace only.@Size(min, max)— String or collection length bounds.@Positive/@Min/@Max— Numeric range checks.@Pattern(regexp)— Regex match for formats like ISBN.@Email— Email format validation.
Path variable and query param validation
@GetMapping("/search")
public List<BookSummary> search(
@RequestParam @NotBlank @Size(min = 2) String q) {
return bookService.search(q);
}
Add @Validated on the controller class for method-parameter validation:
@RestController
@Validated
@RequestMapping("/api/books")
public class BookController { ... }
Without @Validated on the class, constraints on @RequestParam and @PathVariable are ignored.
Custom validator
ISBN-13 has a check digit algorithm:
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = IsbnValidator.class)
public @interface ValidIsbn {
String message() default "Invalid ISBN-13";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class IsbnValidator implements ConstraintValidator<ValidIsbn, String> {
@Override
public boolean isValid(String isbn, ConstraintValidatorContext ctx) {
if (isbn == null) return true; // @NotNull handles null
String digits = isbn.replace("-", "");
if (!digits.matches("\\d{13}")) return false;
int sum = 0;
for (int i = 0; i < 12; i++) {
sum += (digits.charAt(i) - '0') * (i % 2 == 0 ? 1 : 3);
}
int check = (10 - sum % 10) % 10;
return check == (digits.charAt(12) - '0');
}
}
Usage:
public record CreateBookRequest(
@NotBlank String title,
@ValidIsbn String isbn,
@Positive BigDecimal price
) {}
Validation groups
Different rules for create vs update:
public interface OnCreate {}
public interface OnUpdate {}
public record BookRequest(
@Null(groups = OnCreate.class)
@NotNull(groups = OnUpdate.class)
Long id,
@NotBlank(groups = {OnCreate.class, OnUpdate.class})
String title
) {}
@PostMapping
public BookResponse create(@RequestBody @Validated(OnCreate.class) BookRequest req) { ... }
Error response format
Handled by @RestControllerAdvice (see exception-handling article):
{
"title": "Validation Failed",
"status": 400,
"errors": {
"isbn": "Invalid ISBN-13",
"price": "Price must be positive"
}
}
Programmatic validation
For validation outside the web layer:
@Service
public class ImportService {
private final Validator validator;
public ImportService(Validator validator) {
this.validator = validator;
}
public void importBook(CreateBookRequest request) {
Set<ConstraintViolation<CreateBookRequest>> violations =
validator.validate(request);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
// proceed with import
}
}
Useful for CSV imports or message queue consumers that bypass controllers.
Quick recall
Everything you need if you only revisit this box.
spring-boot-starter-validationbrings Hibernate Validator to Boot.@Validon@RequestBodyvalidates request DTOs before the handler runs.@Validatedon the controller class enables constraints on@RequestParamand@PathVariable.- Built-in constraints cover null, blank, size, range, and pattern checks.
- Custom validators implement
ConstraintValidatorfor domain rules like ISBN check digits. - Validation groups allow different constraint sets for create vs update operations.
Test yourself
Answer these before moving on — recall is what makes it stick.