Validation

About

Jakarta Validation (formerly Bean Validation) is a Java specification that allows us to declaratively define constraints on object models (beans) and validate them at runtime. It is defined by the Jakarta EE platform and comes under the package jakarta.validation.

Originally part of the javax.validation package, it was renamed as part of the Jakarta EE transition. Jakarta Validation can be used in any Java SE application.

Refer to the following pages fro more details - javax.validation & jakarta.validation

Primary Use Cases

  • Validating user input (e.g., REST API requests)

  • Validating data transfer objects (DTOs)

  • Validating method parameters and return values

  • Applying cross-field and class-level validations

Important Terminology

Term
Description

Constraint

A rule that must be satisfied by a field or object

Constraint Validator

A class that checks whether a given constraint is satisfied

Constraint Violation

A validation failure message returned after validation

Metadata

The annotations and their parameters defined on fields/methods

ConstraintDescriptor

Runtime metadata for each constraint

Validator

Main interface to perform validation operations

@Valid

Annotation to enable recursive/nested validation

Group

Allows validating only a specific subset of constraints

Example: Basic Flow

Execution Steps (Internal):

  1. At runtime, Validator reflects on the class User.

  2. For each field:

    • It checks what constraint annotations are present.

    • Each constraint is mapped to a ConstraintValidator implementation.

  3. The field value is extracted (e.g., username = "ab").

  4. The validator (SizeValidator) is executed.

  5. If validation fails, it creates a ConstraintViolation with metadata like:

    • invalid value

    • message

    • path

    • constraint type

  6. The process is repeated for all constraints and fields.

  7. The result is a Set<ConstraintViolation<T>>.

Validator Interface and ConstraintValidator

  • jakarta.validation.Validator: Entry point to the validation engine.

  • jakarta.validation.ConstraintValidator<A, T>: Generic interface to implement custom validation logic.

How a Validator Works Internally

Let’s walk through the execution using @Email.

  1. The validation engine sees @Email.

  2. The EmailValidator class is registered as the implementation of ConstraintValidator<Email, String>.

  3. During validation:

    • The initialize() method of EmailValidator is called (if needed).

    • Then, the isValid(value, context) method is called.

    • Inside isValid, logic like regex matching or domain checking is applied.

Code behind the scenes:

Constraint Declaration and Meta-Annotations

Every annotation like @Email, @NotNull is:

  • Annotated with @Constraint(validatedBy = X.class)

  • Annotated with @Target, @Retention

  • Must define message, groups, payload

Example: Behind @NotNull

How Recursive / Nested Validation Works (@Valid)

Scenario:

What happens when we validate Order?

  • When Validator.validate(order) is called:

    1. It sees @Valid on customer field.

    2. It recursively calls validate(customer).

    3. Then applies @NotNull and @Email on the Customer fields.

  • If @Valid is not present, the nested object is skipped.

Validation Groups

We use Validation Groups if we want different validation rules in different scenarios (e.g., Create vs Update).

Step 1: Define groups

Step 2: Use in model

Step 3: Validate by group

Last updated