Validation with Hibernate Validator

This section documents Hibernate ORM 7.4.x (User Guide, Introduction, Query Language Guide, Data Repositories Guide), Jakarta Persistence 3.2, Hibernate Search 8.4.x, and the Hibernate Validator / Hibernate Reactive references — which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against those official docs before being relied on in production.

Three older reference books were consulted as bibliography only while preparing these pages and are not the primary or main source for any page. All three predate Jakarta Persistence 3.2 and Hibernate ORM 6/7 (the javax.persistencejakarta.persistence namespace change, the ORM 6 query-engine rewrite, the Hibernate Search 6+ Elasticsearch backend), so the official documentation above wins on any discrepancy.

This section’s bibliography lists the reference material consulted while preparing these pages.

Hibernate Validator is the reference implementation of Jakarta Validation (Bean Validation) — despite the shared "Hibernate" name, it is a separate specification and library from Hibernate ORM, usable entirely on its own without any JPA/ORM involvement at all. This page covers it as a general validation library and its two points of contact with the rest of this section: JPA pre-persist/pre-update validation, and Spring MVC request validation.

Built-in constraints

public class BookRequest {
    @NotBlank
    private String title;

    @NotNull
    @Positive
    private BigDecimal price;

    @Size(min = 10, max = 13)
    private String isbn;

    @Email
    private String contactEmail;

    @Past
    private LocalDate firstEditionDate;
}

The standard set covers most cases directly: @NotNull/@NotBlank/@NotEmpty, @Size, @Min/@Max, @Positive/@Negative (and their OrZero variants), @Email, @Pattern, @Past/@Future, @Digits. Hibernate Validator additionally ships a set of non-standard extensions (@URL, @Length, @Range, @CreditCardNumber, @ISBN, @DurationMin/@DurationMax) not part of the Jakarta Validation specification itself, so code depending on them is tied to Hibernate Validator specifically rather than portable to another implementation.

@Valid cascading

@Valid (not @Validated — see the Spring-specific section below) triggers validation of a nested object graph:

public class OrderRequest {
    @NotNull
    @Valid
    private CustomerRequest customer; // CustomerRequest's own constraints are also checked

    @NotEmpty
    @Valid
    private List<@Valid OrderLineRequest> lines; // each element validated too
}

Without @Valid on the nested field/collection element, only the container object’s own directly-declared constraints are checked — constraints on the nested type are silently skipped.

Custom `ConstraintValidator`s

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IsbnFormatValidator.class)
public @interface ValidIsbnFormat {
    String message() default "not a valid ISBN-13 format";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class IsbnFormatValidator implements ConstraintValidator<ValidIsbnFormat, String> {
    private static final Pattern ISBN_13 = Pattern.compile("97[89]\\d{10}");

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value == null || ISBN_13.matcher(value).matches();
    }
}

A custom constraint pairs an annotation (declaring message/groups/payload, required by the specification) with a ConstraintValidator<Annotation, Type> implementation containing the actual check logic.

Constraint groups

Groups let the same object be validated differently depending on context, without duplicating the object’s class:

public interface OnCreate {}
public interface OnUpdate {}

public class BookRequest {
    @Null(groups = OnCreate.class)      // must be absent on create ...
    @NotNull(groups = OnUpdate.class)   // ... but required on update
    private Long id;
}

// ...
Set<ConstraintViolation<BookRequest>> violations = validator.validate(request, OnCreate.class);

A constraint with no explicit groups() belongs to the implicit Default group, checked whenever validation runs without naming a specific group.

Method-level validation

Jakarta Validation also validates method parameters and return values directly, independent of any web framework:

public class BookService {
    public Book findBook(@NotNull Long id) { /* ... */ }

    @NotNull
    public Book createBook(@Valid @NotNull BookRequest request) { /* ... */ }
}

This requires a method-validation-aware proxy around the bean (Hibernate Validator’s own ExecutableValidator, or, under Spring, MethodValidationPostProcessor/@Validated at the class level — see below) to actually intercept calls and check the annotated parameters/return value; the annotations alone do nothing without that interception layer in place.

How it plugs into JPA

Jakarta Persistence integrates Jakarta Validation directly: by default, every entity’s constraints are checked automatically immediately before the corresponding INSERT/UPDATE is sent (JPA’s own pre-persist/pre-update lifecycle validation, layered on top of the JPA lifecycle callback mechanism) — ConstraintViolationException is thrown at flush time, before the SQL executes, if any managed entity fails validation. This is a safety net, not a substitute for validating input at the API boundary — catching a violation this late means the failing state has already flowed through the whole service layer. jakarta.persistence.validation.mode (AUTO/CALLBACK/NONE) controls whether this automatic check runs at all; AUTO (the default) enables it whenever a Jakarta Validation provider is present on the classpath.

How it plugs into Spring MVC

Two distinct annotations that are easy to conflate:

  • @Valid on a @RequestBody/@ModelAttribute controller parameter — triggers Spring MVC’s own request-body validation, throwing MethodArgumentNotValidException on failure.

  • @Validated (Spring’s own annotation, not part of Jakarta Validation) on a @Service/@Component class — enables Spring’s AOP-based method-level validation (the "method-level validation" mechanism above) for every method of that bean, and, with a group argument (@Validated(OnCreate.class)), selects which constraint group applies.

MethodArgumentNotValidException (from a failed @Valid request body) is the standard case REST APIs' @ExceptionHandler/ProblemDetail machinery is built to translate into a structured RFC 9457 error response — see that page’s ProblemDetail and RFC 9457 section for the translation itself.