PrepZone Logo
PrepZone

Bean Validation in Practice

@Valid, built-in constraints, custom validators and validation groups.

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.
HTTP request
DispatcherServlet
Controller
Service
JSON response
From HTTP arrival to JSON response — know where validation, security and exception handling sit.

Enable validation

Add the validation starter:

Java
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Annotate the controller parameter:

Java
@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

Java
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

Java
@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:

Java
@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:

Java
@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:

Java
public record CreateBookRequest(
    @NotBlank String title,
    @ValidIsbn String isbn,
    @Positive BigDecimal price
) {}

Validation groups

Different rules for create vs update:

Java
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
) {}
Java
@PostMapping
public BookResponse create(@RequestBody @Validated(OnCreate.class) BookRequest req) { ... }

Error response format

Handled by @RestControllerAdvice (see exception-handling article):

Java
{
  "title": "Validation Failed",
  "status": 400,
  "errors": {
    "isbn": "Invalid ISBN-13",
    "price": "Price must be positive"
  }
}

Programmatic validation

For validation outside the web layer:

Java
@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-validation brings Hibernate Validator to Boot.
  • @Valid on @RequestBody validates request DTOs before the handler runs.
  • @Validated on the controller class enables constraints on @RequestParam and @PathVariable.
  • Built-in constraints cover null, blank, size, range, and pattern checks.
  • Custom validators implement ConstraintValidator for 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.