Jakarta Bean Validation Interview Questions and Answers

Master Jakarta Bean Validation with production-ready interview questions covering built-in constraints, nested validation, validation groups, custom annotations, custom validators, method validation, REST integration, error responses, and senior-level best practices.


Jakarta Bean Validation Interview Questions and Answers

Introduction

Jakarta Bean Validation provides a standard annotation-based model for validating Java objects, method parameters, return values, and nested object graphs.

It allows developers to declare rules such as:

  • A value must not be null
  • A string must not be blank
  • An email must have a valid format
  • A number must be within a range
  • A collection must contain a specific number of elements
  • A nested request object must also be validated
  • A field combination must satisfy a business-oriented rule

Jakarta Bean Validation is commonly integrated with:

  • Jakarta REST
  • Jakarta Persistence
  • Jakarta CDI
  • Jakarta Transactions
  • Spring MVC
  • Spring Boot
  • Hibernate Validator

A production-ready validation design must clearly separate:

  • Transport validation
  • Structural validation
  • Domain invariants
  • Authorization checks
  • Database constraints
  • Cross-field validation
  • External-system validation

Bean Validation is powerful, but it should not become a replacement for domain logic, database constraints, security checks, or workflow validation.

This guide covers the 15 most frequently asked Jakarta Bean Validation interview questions with code examples, Mermaid diagrams, production scenarios, common mistakes, and senior-level recommendations.


Q1. What is Jakarta Bean Validation?

Answer

Jakarta Bean Validation is the Jakarta EE specification for declarative object validation.

It allows validation rules to be placed directly on:

  • Fields
  • Properties
  • Constructor parameters
  • Method parameters
  • Return values
  • Classes
  • Nested objects

Basic DTO Example

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record CreateCustomerRequest(

    @NotBlank
    @Size(max = 100)
    String name,

    @NotBlank
    @Email
    String email
) {
}

Validation Flow

flowchart TD
    A[Incoming Request] --> B[Create Request DTO]
    B --> C[Bean Validation Engine]
    C --> D{All Constraints Pass?}
    D -->|Yes| E[Invoke Business Service]
    D -->|No| F[Return Validation Errors]

Main Benefits

  • Declarative validation
  • Reusable constraints
  • Consistent error detection
  • Integration with Jakarta REST
  • Integration with persistence lifecycle
  • Support for nested validation
  • Support for custom constraints
  • Support for method validation

Why Interviewers Ask This

Interviewers want to know whether you understand validation as a standard platform capability rather than a collection of manual if statements.

Production Example

A customer-registration API validates name, email, phone number, and address before calling the application service.

Common Mistake

Describing Bean Validation as only form validation.

Senior Follow-up

Bean Validation can validate object state, executable parameters, return values, and complete object graphs.


Q2. Why is Bean Validation used?

Answer

Bean Validation is used to centralize structural and reusable input rules.

Without Bean Validation:

if (
    request.name() == null
    || request.name().isBlank()
) {
    throw new IllegalArgumentException(
        "Name is required"
    );
}

if (
    request.email() == null
    || !request.email().contains("@")
) {
    throw new IllegalArgumentException(
        "Email is invalid"
    );
}

With Bean Validation:

public record CreateCustomerRequest(

    @NotBlank
    String name,

    @NotBlank
    @Email
    String email
) {
}

Benefits

  • Less repetitive code
  • Standard constraint vocabulary
  • Consistent validation
  • Reusable custom annotations
  • Better API integration
  • Easier testing
  • Better documentation
  • Cleaner service methods

Validation Responsibility

flowchart TD
    A[Input Data] --> B[Structural Validation]
    B --> C[Bean Validation]
    C --> D[Business Validation]
    D --> E[Authorization]
    E --> F[Persistence]
    F --> G[Database Constraints]

Common Mistake

Using Bean Validation for every business decision.

Senior Follow-up

Bean Validation is strongest for declarative constraints. Complex workflow rules often belong in domain services or entities.


Q3. What does @NotNull do?

Answer

@NotNull requires a value to be non-null.

It does not validate:

  • Empty strings
  • Blank strings
  • Empty collections

Example

public record TransferRequest(

    @NotNull
    Long sourceAccountId,

    @NotNull
    Long destinationAccountId,

    @NotNull
    BigDecimal amount
) {
}

Behavior

Value @NotNull Result
null Invalid
"" Valid
" " Valid
Empty list Valid
0 Valid

Validation Flow

flowchart LR
    A[Field Value] --> B{Value is null?}
    B -->|Yes| C[Constraint Violation]
    B -->|No| D[Valid for @NotNull]

Example Entity

@Entity
public class Payment {

    @NotNull
    @Column(nullable = false)
    private BigDecimal amount;
}

Important Distinction

@NotNull
@Column(nullable = false)

These serve different layers:

  • @NotNull validates the Java object.
  • nullable = false influences schema mapping.
  • A database NOT NULL constraint protects stored data.

Common Mistake

Assuming @NotNull prevents empty text.

Senior Follow-up

Use the most specific constraint for the data type and business meaning.


Q4. @NotNull vs @NotEmpty vs @NotBlank?

Answer

These constraints validate different conditions.

Comparison

Constraint Rejects Null Rejects Empty Rejects Whitespace-Only
@NotNull Yes No No
@NotEmpty Yes Yes No
@NotBlank Yes Yes Yes

Example

public record UserRequest(

    @NotNull
    String externalId,

    @NotEmpty
    List<String> roles,

    @NotBlank
    String displayName
) {
}

String Examples

Input @NotNull @NotEmpty @NotBlank
null Invalid Invalid Invalid
"" Valid Invalid Invalid
" " Valid Valid Invalid
"Venu" Valid Valid Valid

Decision Flow

flowchart TD
    A[Select Constraint] --> B{Only null forbidden?}
    B -->|Yes| C[Use @NotNull]
    B -->|No| D{String must contain non-space text?}
    D -->|Yes| E[Use @NotBlank]
    D -->|No| F{Collection or string must not be empty?}
    F -->|Yes| G[Use @NotEmpty]

Common Mistake

Using @NotNull for mandatory user-entered text.

Senior Follow-up

For human-entered required text, @NotBlank is usually more appropriate.


Q5. What are the common built-in validation annotations?

Answer

Jakarta Bean Validation includes many standard constraints.

String Constraints

  • @NotBlank
  • @NotEmpty
  • @Size
  • @Pattern
  • @Email

Numeric Constraints

  • @Min
  • @Max
  • @DecimalMin
  • @DecimalMax
  • @Positive
  • @PositiveOrZero
  • @Negative
  • @NegativeOrZero
  • @Digits

Date and Time Constraints

  • @Past
  • @PastOrPresent
  • @Future
  • @FutureOrPresent

Boolean Constraints

  • @AssertTrue
  • @AssertFalse

Example

public record ProductRequest(

    @NotBlank
    @Size(
        min = 3,
        max = 100
    )
    String name,

    @Positive
    @Digits(
        integer = 10,
        fraction = 2
    )
    BigDecimal price,

    @Min(0)
    @Max(10000)
    Integer stock,

    @FutureOrPresent
    LocalDate availableFrom
) {
}

Constraint Map

flowchart TD
    A[Jakarta Constraints] --> B[String]
    A --> C[Numeric]
    A --> D[Date and Time]
    A --> E[Boolean]
    A --> F[Collection]

    B --> G[NotBlank Size Pattern Email]
    C --> H[Min Max Positive Digits]
    D --> I[Past Future]
    E --> J[AssertTrue AssertFalse]
    F --> K[NotEmpty Size]

Common Mistake

Using @Size for numeric range validation.

Senior Follow-up

@Size measures string, collection, map, or array size. Use numeric constraints for numeric values.


Q6. What does @Valid do?

Answer

@Valid triggers cascading validation of a nested object or collection of objects.

Without @Valid, constraints inside the nested object may not be evaluated.

Address DTO

public record AddressRequest(

    @NotBlank
    String street,

    @NotBlank
    String city,

    @NotBlank
    @Pattern(
        regexp = "\\d{5}"
    )
    String postalCode
) {
}

Customer DTO

public record CreateCustomerRequest(

    @NotBlank
    String name,

    @Valid
    @NotNull
    AddressRequest address
) {
}

Nested Validation Flow

flowchart TD
    A[Validate Customer Request] --> B[Validate Customer Fields]
    B --> C{Address has @Valid?}
    C -->|Yes| D[Validate Address Fields]
    C -->|No| E[Skip Nested Constraints]
    D --> F[Collect All Violations]

Collection Validation

public record CreateOrderRequest(

    @NotEmpty
    List<
        @Valid OrderItemRequest
    > items
) {
}

Common Mistake

Adding constraints to a nested DTO but forgetting @Valid on the parent field.

Senior Follow-up

Object graph validation should be deliberate because deep validation can increase processing cost and produce large error sets.


Q7. How does container element validation work?

Answer

Container element validation applies constraints to elements inside:

  • Lists
  • Sets
  • Maps
  • Optionals
  • Arrays
  • Other supported containers

List Element Validation

public record NotificationRequest(

    @NotEmpty
    List<
        @Email
        @NotBlank
        String
    > recipients
) {
}

Map Validation

public record ConfigurationRequest(

    Map<
        @NotBlank String,
        @NotBlank String
    > properties
) {
}

Optional Validation

public record ProfileRequest(

    Optional<
        @Email String
    > secondaryEmail
) {
}

Flow

flowchart TD
    A[Validate Container] --> B[Validate Container Constraint]
    B --> C[Iterate Elements]
    C --> D[Validate Element Constraints]
    D --> E[Record Element Index or Key]

Example Error Path

recipients[2]

or:

properties[apiUrl]

Common Mistake

Validating only the collection itself and not the values inside it.

Senior Follow-up

Container element validation is useful for precise error reporting in complex request payloads.


Q8. What are validation groups?

Answer

Validation groups allow different constraints to run in different use cases.

A common requirement is applying different rules for:

  • Create
  • Update
  • Publish
  • Approve
  • Internal processing

Marker Interfaces

public interface OnCreate {
}

public interface OnUpdate {
}

DTO

public class CustomerRequest {

    @Null(
        groups = OnCreate.class
    )
    @NotNull(
        groups = OnUpdate.class
    )
    private Long id;

    @NotBlank(
        groups = {
            OnCreate.class,
            OnUpdate.class
        }
    )
    private String name;
}

Validation Invocation

validator.validate(
    request,
    OnCreate.class
);

Group Flow

flowchart TD
    A[Validation Request] --> B{Selected Group}
    B -->|OnCreate| C[Run Create Constraints]
    B -->|OnUpdate| D[Run Update Constraints]
    B -->|Default| E[Run Default Constraints]

Use Cases

  • ID must be null during create
  • ID must be present during update
  • Additional fields required during approval
  • Draft objects allow incomplete state
  • Published objects require strict validation

Common Mistake

Creating many groups until validation behavior becomes difficult to understand.

Senior Follow-up

Use groups sparingly. Separate request DTOs are often clearer than complex group combinations.


Q9. What is a group sequence?

Answer

A group sequence defines the order in which validation groups execute.

If one group fails, later groups are not evaluated.

Example Groups

public interface BasicChecks {
}

public interface BusinessChecks {
}

Sequence

@GroupSequence({
    BasicChecks.class,
    BusinessChecks.class
})
public interface OrderedChecks {
}

Validation

validator.validate(
    request,
    OrderedChecks.class
);

Execution Flow

flowchart TD
    A[Start Validation] --> B[Basic Checks]
    B --> C{Any Violations?}
    C -->|Yes| D[Stop Validation]
    C -->|No| E[Business Checks]
    E --> F[Return Result]

Good Use Case

First validate:

  • Nulls
  • Formats
  • Lengths

Then validate:

  • Expensive cross-field checks
  • Business-oriented custom constraints

Common Mistake

Using group sequences to call databases or external services from validators.

Senior Follow-up

Validation ordering can reduce unnecessary work, but validators should remain deterministic and side-effect free.


Q10. How do you create a custom constraint?

Answer

A custom constraint requires:

  1. A validation annotation
  2. A validator class
  3. Error-message definition
  4. Target and retention metadata

Custom Annotation

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({
    FIELD,
    PARAMETER
})
@Retention(RUNTIME)
@Constraint(
    validatedBy =
        ValidAccountNumberValidator.class
)
public @interface ValidAccountNumber {

    String message()
        default
        "Account number is invalid";

    Class<?>[] groups()
        default {};

    Class<
        ? extends Payload
    >[] payload()
        default {};
}

Validator

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class ValidAccountNumberValidator
    implements ConstraintValidator<
        ValidAccountNumber,
        String
    > {

    @Override
    public boolean isValid(
        String value,
        ConstraintValidatorContext context
    ) {

        if (value == null) {
            return true;
        }

        return value.matches(
            "[A-Z]{2}\\d{10}"
        );
    }
}

Usage

public record PaymentRequest(

    @NotBlank
    @ValidAccountNumber
    String accountNumber
) {
}

Custom Constraint Architecture

flowchart TD
    A[Custom Annotation] --> B[Constraint Metadata]
    B --> C[Constraint Validator]
    C --> D[Validate Field Value]
    D --> E{Valid?}
    E -->|Yes| F[Continue]
    E -->|No| G[Create Violation]

Common Mistake

Making null invalid in a custom validator while also using @NotNull, causing duplicate responsibilities.

Senior Follow-up

Custom validators commonly treat null as valid and delegate presence requirements to @NotNull or @NotBlank.


Q11. How do you create a cross-field validation constraint?

Answer

Cross-field validation applies a constraint to the complete class instead of one field.

Use Case

A password confirmation must match the password.

Annotation

@Target(TYPE)
@Retention(RUNTIME)
@Constraint(
    validatedBy =
        PasswordMatchesValidator.class
)
public @interface PasswordMatches {

    String message()
        default
        "Passwords must match";

    Class<?>[] groups()
        default {};

    Class<
        ? extends Payload
    >[] payload()
        default {};
}

DTO

@PasswordMatches
public record RegistrationRequest(

    @NotBlank
    String password,

    @NotBlank
    String confirmPassword
) {
}

Validator

public class PasswordMatchesValidator
    implements ConstraintValidator<
        PasswordMatches,
        RegistrationRequest
    > {

    @Override
    public boolean isValid(
        RegistrationRequest request,
        ConstraintValidatorContext context
    ) {

        if (request == null) {
            return true;
        }

        boolean valid =
            Objects.equals(
                request.password(),
                request.confirmPassword()
            );

        if (!valid) {
            context.disableDefaultConstraintViolation();

            context
                .buildConstraintViolationWithTemplate(
                    "Password confirmation does not match"
                )
                .addPropertyNode(
                    "confirmPassword"
                )
                .addConstraintViolation();
        }

        return valid;
    }
}

Cross-Field Flow

flowchart TD
    A[Validate Complete DTO] --> B[Read Password]
    B --> C[Read Confirmation]
    C --> D{Values Match?}
    D -->|Yes| E[Valid]
    D -->|No| F[Add Field-Level Violation]

Other Use Cases

  • Start date before end date
  • At least one contact method supplied
  • Currency and amount combination
  • Country and postal code compatibility
  • Conditional mandatory fields

Common Mistake

Using field-level validators when multiple fields are required to make the decision.

Senior Follow-up

Class-level constraints should produce precise property paths where possible so clients know which field needs correction.


Q12. How is Bean Validation integrated with Jakarta REST?

Answer

Jakarta REST can automatically validate resource parameters and request DTOs.

Request DTO

public record CreateClaimRequest(

    @NotBlank
    String memberId,

    @Positive
    BigDecimal amount,

    @Valid
    @NotNull
    ClaimantRequest claimant
) {
}

Resource

@Path("/claims")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class ClaimResource {

    private final ClaimService claimService;

    @Inject
    public ClaimResource(
        ClaimService claimService
    ) {
        this.claimService = claimService;
    }

    @POST
    public Response createClaim(
        @Valid
        CreateClaimRequest request
    ) {

        Long claimId =
            claimService.createClaim(
                request
            );

        return Response
            .status(
                Response.Status.CREATED
            )
            .entity(
                new CreateClaimResponse(
                    claimId
                )
            )
            .build();
    }
}

Parameter Validation

@GET
public PageResponse<ClaimSummary>
    searchClaims(

        @QueryParam("page")
        @DefaultValue("0")
        @Min(0)
        int page,

        @QueryParam("size")
        @DefaultValue("20")
        @Min(1)
        @Max(100)
        int size
    ) {

    return claimService.search(
        page,
        size
    );
}

Runtime Flow

sequenceDiagram
    participant Client
    participant REST
    participant Validator
    participant Resource
    participant Service
    Client->>REST: HTTP request
    REST->>Validator: Validate DTO and parameters
    alt Valid
        Validator->>Resource: Invoke method
        Resource->>Service: Execute use case
        Service-->>Client: Success
    else Invalid
        Validator-->>Client: Validation response
    end

Common Mistake

Calling the service before validation has completed.

Senior Follow-up

API-boundary validation should reject malformed requests before business transactions begin.


Q13. What is method-level validation?

Answer

Method-level validation validates:

  • Method parameters
  • Constructor parameters
  • Method return values

Service Example

@ApplicationScoped
public class PaymentService {

    public PaymentResponse process(

        @NotNull
        Long accountId,

        @Positive
        BigDecimal amount
    ) {
        return new PaymentResponse();
    }
}

Return Value Validation

@NotNull
public CustomerResponse findCustomer(
    Long customerId
) {
    return customerRepository
        .findResponse(customerId);
}

Constructor Validation

public PaymentCommand(

    @NotNull
    Long accountId,

    @Positive
    BigDecimal amount
) {
}

Executable Validation Flow

flowchart TD
    A[Method Invocation] --> B[Validate Parameters]
    B --> C{Valid?}
    C -->|No| D[Throw Constraint Violation]
    C -->|Yes| E[Execute Method]
    E --> F[Validate Return Value]
    F --> G{Return Valid?}
    G -->|Yes| H[Return Result]
    G -->|No| I[Throw Constraint Violation]

Production Uses

  • Guard service contracts
  • Validate programmatic API usage
  • Enforce reusable library contracts
  • Verify return guarantees

Common Mistake

Assuming method validation always applies automatically to self-invocation.

Senior Follow-up

Method validation is commonly interceptor-based, so proxy and invocation boundaries matter.


Q14. How should validation errors be handled?

Answer

Validation errors should be converted into a stable, client-friendly response format.

Error Model

public record ValidationErrorResponse(
    String code,
    String message,
    String correlationId,
    List<FieldViolation> violations
) {
}
public record FieldViolation(
    String field,
    String message,
    Object rejectedValue
) {
}

Example JSON

{
  "code": "VALIDATION_FAILED",
  "message": "Request validation failed",
  "correlationId": "a82d5f23-0b70",
  "violations": [
    {
      "field": "email",
      "message": "must be a well-formed email address",
      "rejectedValue": "invalid-email"
    },
    {
      "field": "amount",
      "message": "must be greater than 0",
      "rejectedValue": -10
    }
  ]
}

Mapper Concept

@Provider
public class ConstraintViolationMapper
    implements ExceptionMapper<
        ConstraintViolationException
    > {

    @Override
    public Response toResponse(
        ConstraintViolationException exception
    ) {

        List<FieldViolation> violations =
            exception
                .getConstraintViolations()
                .stream()
                .map(
                    violation ->
                        new FieldViolation(
                            violation
                                .getPropertyPath()
                                .toString(),
                            violation
                                .getMessage(),
                            safeRejectedValue(
                                violation
                                    .getInvalidValue()
                            )
                        )
                )
                .toList();

        return Response
            .status(
                Response.Status.BAD_REQUEST
            )
            .entity(
                new ValidationErrorResponse(
                    "VALIDATION_FAILED",
                    "Request validation failed",
                    correlationId(),
                    violations
                )
            )
            .build();
    }
}

Error Handling Flow

flowchart TD
    A[Constraint Violations] --> B[Exception Mapper]
    B --> C[Normalize Property Paths]
    C --> D[Remove Sensitive Values]
    D --> E[Build Stable Error Contract]
    E --> F[Return HTTP 400 or 422]

Security Considerations

Do not return sensitive rejected values such as:

  • Passwords
  • Tokens
  • Card numbers
  • Health data
  • Security answers

Common Mistake

Returning the raw ConstraintViolationException to the client.

Senior Follow-up

Validation error codes and field paths should remain stable enough for frontend and mobile clients to consume reliably.


Q15. What are senior-level Bean Validation best practices?

Answer

Senior developers should use Bean Validation for declarative rules while keeping business behavior, security, and persistence guarantees in their proper layers.

Best Practices

  • Use the most specific constraint.
  • Prefer @NotBlank for mandatory user text.
  • Use @Valid for nested object graphs.
  • Validate collection elements.
  • Keep custom validators side-effect free.
  • Avoid remote calls inside validators.
  • Avoid database writes inside validators.
  • Use class-level constraints for cross-field rules.
  • Return precise field paths.
  • Keep validation messages client-friendly.
  • Do not expose sensitive rejected values.
  • Use groups sparingly.
  • Prefer separate DTOs when group complexity grows.
  • Keep API validation separate from domain invariants.
  • Keep authorization separate from validation.
  • Back critical rules with database constraints.
  • Test boundary values.
  • Test nested and collection validation.
  • Internationalize messages when required.
  • Monitor validation failure rates.

Validation Architecture

flowchart TD
    A[Client Request] --> B[Transport Validation]
    B --> C[Bean Validation]
    C --> D[Application Service]
    D --> E[Business Rules]
    E --> F[Authorization]
    F --> G[Domain Invariants]
    G --> H[Persistence]
    H --> I[Database Constraints]

Decision Flow

flowchart TD
    A[New Validation Rule] --> B{Single field and declarative?}
    B -->|Yes| C[Built-In Constraint]
    B -->|No| D{Multiple fields involved?}
    D -->|Yes| E[Class-Level Constraint]
    D -->|No| F{Workflow or external state required?}
    F -->|Yes| G[Service or Domain Validation]
    F -->|No| H[Custom Constraint]

Senior Follow-up

A correct system validates at multiple layers because each layer protects against a different class of failure.


Bean Validation Architecture

flowchart TD
    A[Request DTO] --> B[Constraint Metadata]
    B --> C[Validator Engine]
    C --> D[Built-In Validators]
    C --> E[Custom Validators]
    D --> F[Constraint Violations]
    E --> F
    F --> G[Exception Mapper]
    G --> H[Structured Error Response]

Constraint Lifecycle

sequenceDiagram
    participant Runtime
    participant ValidatorFactory
    participant Validator
    participant ConstraintValidator
    participant Application
    Runtime->>ValidatorFactory: Obtain Validator
    ValidatorFactory-->>Runtime: Validator
    Runtime->>Validator: Validate object
    Validator->>ConstraintValidator: Initialize
    Validator->>ConstraintValidator: isValid value
    ConstraintValidator-->>Validator: Result
    Validator-->>Application: Violations

Validation Layers

flowchart LR
    A[Client] --> B[Request Validation]
    B --> C[Service Validation]
    C --> D[Domain Validation]
    D --> E[Database Constraints]

Request Validation

Examples:

  • Required field
  • Valid format
  • Maximum length
  • Numeric range

Service Validation

Examples:

  • Customer exists
  • Account is active
  • Product is available
  • Request is allowed in current workflow

Domain Validation

Examples:

  • Order cannot be submitted without items
  • Transfer cannot debit below allowed balance
  • Claim status transition must be legal

Database Validation

Examples:

  • Unique email
  • Foreign key
  • Not-null column
  • Check constraint

Validation vs Authorization

Validation asks:

Is this input structurally and semantically acceptable?

Authorization asks:

Is this caller permitted to perform this action?

Example

A valid customer ID may still refer to a customer the caller is not allowed to access.

flowchart TD
    A[Valid Request Format] --> B[Load Customer]
    B --> C{Caller Authorized?}
    C -->|Yes| D[Continue]
    C -->|No| E[Return Forbidden]

Important

Do not expose resource existence through validation errors when authorization rules require concealment.


Validation vs Database Constraints

Bean Validation Database Constraint
Runs in application Runs in database
Better client feedback Final data-integrity protection
Can validate DTOs Protects all writers
Can validate complex object graph Strong for relational constraints
May be bypassed by other systems Cannot be bypassed by normal writes
Good for usability Good for integrity

Example

@NotBlank
@Column(
    nullable = false,
    length = 100
)
private String name;

Database migration:

ALTER TABLE customers
ALTER COLUMN name SET NOT NULL;

Both layers are valuable.


Common Built-In Constraint Examples

Email

@Email
private String email;

Pattern

@Pattern(
    regexp = "[A-Z]{3}-\\d{6}"
)
private String claimNumber;

Size

@Size(
    min = 1,
    max = 50
)
private List<OrderItemRequest> items;

Decimal Range

@DecimalMin(
    value = "0.01"
)
@DecimalMax(
    value = "1000000.00"
)
private BigDecimal amount;

Digits

@Digits(
    integer = 12,
    fraction = 2
)
private BigDecimal total;

Date

@Past
private LocalDate dateOfBirth;

Validation Message Customization

Inline Message

@NotBlank(
    message =
        "Customer name is required"
)
private String name;

Message Bundle

customer.name.required=
Customer name is required

customer.email.invalid=
Customer email is invalid

Usage:

@NotBlank(
    message =
        "{customer.name.required}"
)
private String name;

Message Resolution

flowchart TD
    A[Constraint Message Key] --> B[Validation Message Bundle]
    B --> C[Resolve Locale]
    C --> D[Formatted Message]

Internationalization

Validation messages can vary by locale.

Example bundles:

ValidationMessages.properties
ValidationMessages_es.properties
ValidationMessages_fr.properties

Best Practices

  • Keep error code stable.
  • Localize human-readable message.
  • Do not make clients depend only on translated text.
  • Use locale from trusted request context.

Custom Phone Validation

Annotation

@Target({
    FIELD,
    PARAMETER
})
@Retention(RUNTIME)
@Constraint(
    validatedBy =
        PhoneNumberValidator.class
)
public @interface ValidPhoneNumber {

    String message()
        default
        "Phone number is invalid";

    Class<?>[] groups()
        default {};

    Class<
        ? extends Payload
    >[] payload()
        default {};
}

Validator

public class PhoneNumberValidator
    implements ConstraintValidator<
        ValidPhoneNumber,
        String
    > {

    private static final Pattern
        PHONE_PATTERN =
            Pattern.compile(
                "^\\+?[1-9]\\d{7,14}$"
            );

    @Override
    public boolean isValid(
        String value,
        ConstraintValidatorContext context
    ) {

        if (value == null) {
            return true;
        }

        return PHONE_PATTERN
            .matcher(value)
            .matches();
    }
}

Conditional Validation Example

Requirement:

If contact method is EMAIL, email is required.
If contact method is PHONE, phone number is required.

DTO

@ValidContactMethod
public record ContactRequest(
    ContactMethod method,
    String email,
    String phone
) {
}

Flow

flowchart TD
    A[Contact Request] --> B{Selected Method}
    B -->|EMAIL| C[Require Valid Email]
    B -->|PHONE| D[Require Valid Phone]
    B -->|Other| E[Apply Relevant Rule]

This is a good class-level validation use case.


Validation of Collections

public record CreateTeamRequest(

    @NotBlank
    String teamName,

    @Size(
        min = 1,
        max = 20
    )
    List<
        @NotBlank
        String
    > memberNames
) {
}

Nested Collection

public record CreateOrderRequest(

    @NotEmpty
    @Size(max = 100)
    List<
        @Valid
        OrderItemRequest
    > items
) {
}

Validation of Map Values

public record MetadataRequest(

    @Size(max = 20)
    Map<
        @NotBlank
        @Size(max = 50)
        String,

        @NotBlank
        @Size(max = 500)
        String
    > metadata
) {
}

Use limits to prevent unbounded request payloads.


Validation Groups Example

Groups

public interface Draft {
}

public interface Submit {
}

Request

public class ClaimRequest {

    @NotBlank(
        groups = {
            Draft.class,
            Submit.class
        }
    )
    private String memberId;

    @NotNull(
        groups = Submit.class
    )
    @Positive(
        groups = Submit.class
    )
    private BigDecimal amount;

    @NotEmpty(
        groups = Submit.class
    )
    private List<
        @Valid ClaimLineRequest
    > lines;
}

Behavior

flowchart LR
    A[Save Draft] --> B[Draft Group]
    B --> C[Minimal Validation]

    D[Submit Claim] --> E[Submit Group]
    E --> F[Strict Validation]

Separate DTOs vs Validation Groups

Separate DTOs

public record CreateCustomerRequest(
    String name,
    String email
) {
}
public record UpdateCustomerRequest(
    String name,
    String email
) {
}

Validation Groups

One DTO with group-specific annotations.

Comparison

Separate DTOs Validation Groups
Clear use-case contract Less duplicated structure
Easier API documentation Centralized field model
Easier reasoning Can become complex
More classes Fewer classes
Stronger boundary separation Useful for shared workflows

Senior Recommendation

Prefer separate DTOs for clearly different API operations. Use groups for genuinely shared validation workflows.


Custom Constraint with Parameters

Annotation

@Target(FIELD)
@Retention(RUNTIME)
@Constraint(
    validatedBy =
        AllowedValuesValidator.class
)
public @interface AllowedValues {

    String[] values();

    boolean ignoreCase()
        default true;

    String message()
        default
        "Value is not allowed";

    Class<?>[] groups()
        default {};

    Class<
        ? extends Payload
    >[] payload()
        default {};
}

Usage

@AllowedValues(
    values = {
        "EMAIL",
        "SMS",
        "PUSH"
    }
)
private String channel;

Programmatic Validation

@Inject
private Validator validator;
Set<
    ConstraintViolation<
        CreateCustomerRequest
    >
> violations =
    validator.validate(request);

if (!violations.isEmpty()) {
    throw new ConstraintViolationException(
        violations
    );
}

Use Cases

  • Batch file validation
  • Message validation
  • Manually constructed objects
  • Workflow-specific validation
  • Unit tests

Batch Validation Flow

flowchart TD
    A[Read Batch Row] --> B[Map to DTO]
    B --> C[Programmatic Validation]
    C --> D{Valid?}
    D -->|Yes| E[Process Row]
    D -->|No| F[Record Row Errors]
    F --> G[Continue or Stop Based on Policy]

Best Practices

  • Include row number.
  • Include field path.
  • Limit number of returned errors.
  • Avoid retaining huge violation sets.
  • Stream validation results.

Validation in Messaging

A consumer should validate message contracts before business processing.

public void onMessage(
    OrderCreatedEvent event
) {

    Set<
        ConstraintViolation<
            OrderCreatedEvent
        >
    > violations =
        validator.validate(event);

    if (!violations.isEmpty()) {
        throw new InvalidMessageException(
            violations
        );
    }

    orderService.process(event);
}

Message Flow

flowchart TD
    A[Receive Message] --> B[Deserialize Contract]
    B --> C[Validate Message]
    C --> D{Valid?}
    D -->|Yes| E[Process Business Logic]
    D -->|No| F[Reject or Route to DLQ]

Validation and JPA Lifecycle

Bean Validation may run before persistence operations.

Common lifecycle integration points include:

  • Before persist
  • Before update
  • Before remove, if configured

Flow

sequenceDiagram
    participant Service
    participant JPA
    participant Validator
    participant Database
    Service->>JPA: persist entity
    JPA->>Validator: Validate entity
    alt Valid
        Validator-->>JPA: Success
        JPA->>Database: INSERT
    else Invalid
        Validator-->>JPA: Constraint violations
        JPA-->>Service: Exception
    end

Important

Do not rely only on persistence-time validation for API feedback. Validate requests earlier.


Validation Performance Considerations

Potential costs include:

  • Deep object graph traversal
  • Large collections
  • Expensive regular expressions
  • Repeated custom validation
  • Large error sets
  • Reflection and metadata processing
  • Message interpolation

Performance Flow

flowchart TD
    A[Large Object Graph] --> B[Recursive Validation]
    B --> C[Many Collection Elements]
    C --> D[Many Constraint Checks]
    D --> E[Higher CPU and Memory]

Best Practices

  • Limit request size.
  • Limit collection size.
  • Avoid catastrophic regular expressions.
  • Avoid remote calls.
  • Avoid database access.
  • Stop early with group sequences when useful.
  • Cache immutable supporting metadata carefully.

Regular Expression Safety

Unsafe or overly complex patterns can cause excessive CPU usage.

Safer Approach

  • Keep expressions simple.
  • Test worst-case inputs.
  • Limit input length.
  • Prefer dedicated parsers for complex formats.
  • Avoid using regex to validate complete business documents.

Validation and Security

Validation does not replace:

  • Authentication
  • Authorization
  • Input sanitization for output contexts
  • SQL parameter binding
  • CSRF protection
  • File scanning
  • Rate limiting

Security Flow

flowchart TD
    A[Request] --> B[Authentication]
    B --> C[Authorization]
    C --> D[Validation]
    D --> E[Business Processing]
    E --> F[Output Encoding]

The exact order may vary, but each concern remains distinct.


Common Validation Mistakes

  • Using @NotNull when @NotBlank is required.
  • Forgetting @Valid.
  • Ignoring collection element validation.
  • Putting database calls in validators.
  • Calling external APIs from validators.
  • Mixing authorization with validation.
  • Using groups excessively.
  • Returning sensitive rejected values.
  • Exposing internal property paths.
  • Using vague error messages.
  • Depending only on entity validation.
  • Failing to add database constraints.
  • Using expensive regular expressions.
  • Validating after starting expensive work.
  • Reusing one DTO for unrelated operations.
  • Returning inconsistent error formats.
  • Ignoring localization.
  • Not testing null and boundary conditions.
  • Treating format validation as proof of business validity.
  • Assuming a valid ID belongs to the current user.

Production Error Contract

public record ApiError(
    String code,
    String message,
    String correlationId,
    Instant timestamp,
    List<ApiFieldError> errors
) {
}
public record ApiFieldError(
    String field,
    String code,
    String message
) {
}

Example

{
  "code": "VALIDATION_FAILED",
  "message": "One or more fields are invalid",
  "correlationId": "req-91dc48",
  "timestamp": "2026-07-12T12:00:00Z",
  "errors": [
    {
      "field": "claimant.email",
      "code": "INVALID_EMAIL",
      "message": "Email address is invalid"
    }
  ]
}

Stable machine-readable field error codes are useful for frontend clients.


Testing Built-In Constraints

class CreateCustomerRequestValidationTest {

    private final Validator validator =
        Validation
            .buildDefaultValidatorFactory()
            .getValidator();

    @Test
    void shouldRejectBlankName() {

        CreateCustomerRequest request =
            new CreateCustomerRequest(
                "   ",
                "[email protected]"
            );

        Set<
            ConstraintViolation<
                CreateCustomerRequest
            >
        > violations =
            validator.validate(request);

        assertThat(violations)
            .extracting(
                violation ->
                    violation
                        .getPropertyPath()
                        .toString()
            )
            .contains("name");
    }
}

Testing Custom Constraint

@Test
void shouldRejectInvalidAccountNumber() {

    PaymentRequest request =
        new PaymentRequest(
            "INVALID"
        );

    Set<
        ConstraintViolation<
            PaymentRequest
        >
    > violations =
        validator.validate(request);

    assertThat(violations)
        .isNotEmpty();
}

Test Cases

  • Null
  • Empty
  • Boundary length
  • Valid format
  • Invalid format
  • Lowercase and uppercase
  • Unicode input
  • Very long input
  • Whitespace
  • Malicious pattern input

Testing Cross-Field Constraint

@Test
void shouldRejectMismatchedPasswords() {

    RegistrationRequest request =
        new RegistrationRequest(
            "Secret123!",
            "Different123!"
        );

    Set<
        ConstraintViolation<
            RegistrationRequest
        >
    > violations =
        validator.validate(request);

    assertThat(violations)
        .extracting(
            violation ->
                violation
                    .getPropertyPath()
                    .toString()
        )
        .contains(
            "confirmPassword"
        );
}

Validation Decision Matrix

Requirement Recommended Approach
Required text @NotBlank
Required object @NotNull
Collection not empty @NotEmpty
Length limit @Size
Numeric range @Min, @Max, decimal variants
Positive amount @Positive
Email format @Email
Nested DTO @Valid
Element validation Container element constraint
Cross-field rule Class-level custom constraint
Workflow rule Service or domain validation
Database uniqueness Database constraint plus service handling
Authorization Security layer
External verification Application service

Constraint Composition

Custom annotations can combine multiple constraints.

Example

@NotBlank
@Size(
    min = 8,
    max = 100
)
@Pattern(
    regexp =
        ".*[A-Z].*"
)
@Pattern(
    regexp =
        ".*\\d.*"
)
public @interface StrongPassword {
}

In practice, a composed constraint should be defined carefully with appropriate metadata and reporting behavior.

Composition Flow

flowchart TD
    A[StrongPassword] --> B[NotBlank]
    A --> C[Size]
    A --> D[Uppercase Pattern]
    A --> E[Digit Pattern]

Validation Payload

The payload attribute allows metadata to be associated with a constraint.

public interface Severity {

    class Warning
        implements Payload {
    }

    class Error
        implements Payload {
    }
}

Usage:

@NotBlank(
    payload =
        Severity.Error.class
)
private String name;

Payloads are less commonly used but may support custom reporting or severity classification.


Fail-Fast Validation

Some validation providers support fail-fast mode, where validation stops after the first violation.

Trade-Off

Collect All Violations Fail Fast
Better client usability Lower validation work
More complete response Only first error returned
More CPU for large graphs Faster rejection
Good for forms Useful for internal processing

Use provider-specific fail-fast behavior only when the dependency is acceptable and documented.


Validation in Large File Imports

flowchart TD
    A[Read Row] --> B[Map to Import DTO]
    B --> C[Validate DTO]
    C --> D{Valid?}
    D -->|Yes| E[Add to Write Batch]
    D -->|No| F[Write Error Record]
    E --> G{Batch Full?}
    G -->|Yes| H[Persist Batch]
    G -->|No| A
    H --> A

Error Record Example

public record ImportRowError(
    long rowNumber,
    String field,
    String code,
    String message
) {
}

Validation and Partial Updates

PATCH requests create special validation challenges because fields may be optional but still require validation when supplied.

Example

public record UpdateCustomerRequest(

    @Size(
        min = 2,
        max = 100
    )
    String name,

    @Email
    String email
) {
}

Null can mean:

Field not supplied

A supplied blank string can still be invalid depending on the API contract.

Better Options

  • Dedicated patch model
  • Optional with careful semantics
  • JSON Merge Patch
  • JSON Patch
  • Explicit field-presence wrapper

Validation and Uniqueness

A custom validator that queries the database for uniqueness may suffer from race conditions.

Problem

sequenceDiagram
    participant RequestA
    participant RequestB
    participant Database
    RequestA->>Database: Check email does not exist
    RequestB->>Database: Check email does not exist
    Database-->>RequestA: Not found
    Database-->>RequestB: Not found
    RequestA->>Database: INSERT email
    RequestB->>Database: INSERT same email

Correct Protection

Use a database unique constraint:

ALTER TABLE customers
ADD CONSTRAINT uk_customers_email
UNIQUE (email);

The service can still perform an early check for usability, but the database is the final authority.


Validation Failure Metrics

Monitor:

  • Validation failures per endpoint
  • Most common invalid fields
  • Payload size rejections
  • Malformed request rate
  • Validation CPU time
  • Custom-validator failures
  • Message validation failures
  • Import row error rate

Monitoring Flow

flowchart TD
    A[Validation Failure] --> B[Record Metric]
    B --> C[Aggregate by Endpoint]
    B --> D[Aggregate by Field]
    C --> E[Dashboard]
    D --> E

High validation failure rates may indicate:

  • Poor client integration
  • API documentation gaps
  • Breaking contract changes
  • Malicious traffic
  • User-interface usability issues

Senior Validation Checklist

Correct Constraint Type
        │
        ▼
Nested Validation
        │
        ▼
Collection Element Validation
        │
        ▼
Precise Error Paths
        │
        ▼
No Side Effects
        │
        ▼
No Sensitive Error Values
        │
        ▼
Business Rules in Service or Domain
        │
        ▼
Database Constraints
        │
        ▼
Boundary and Performance Tests

Interview Quick Revision

Concept Purpose
Bean Validation Declarative object validation
@NotNull Reject null
@NotEmpty Reject null and empty
@NotBlank Reject null, empty, and whitespace
@Size Validate length or container size
@Email Validate email format
@Positive Require positive number
@Valid Trigger nested validation
Validation Group Select constraint set
Group Sequence Order validation groups
Custom Constraint Reusable custom rule
Class-Level Constraint Cross-field validation
Method Validation Validate parameters and return values
Constraint Violation Validation failure detail
Validator Executes constraint checks

Key Takeaways

  • Jakarta Bean Validation provides a standard declarative model for validating Java objects, method parameters, return values, and nested object graphs.
  • @NotNull rejects only null values.
  • @NotEmpty rejects null and empty values.
  • @NotBlank rejects null, empty, and whitespace-only text.
  • Built-in constraints support strings, collections, numbers, dates, booleans, and patterns.
  • @Valid is required to cascade validation into nested objects.
  • Container element validation applies constraints to values inside collections, maps, optionals, and other containers.
  • Validation groups allow different rules for different operations, but excessive group usage can reduce clarity.
  • Group sequences provide ordered validation and can stop later checks when earlier groups fail.
  • Custom constraints combine a custom annotation with a ConstraintValidator.
  • Class-level constraints are appropriate for cross-field rules.
  • Custom validators should remain deterministic, side-effect free, and independent of remote systems.
  • Jakarta REST can automatically validate request DTOs, path parameters, query parameters, and return values.
  • Validation errors should be mapped into a stable, structured, client-friendly response.
  • Sensitive rejected values must not be returned in error payloads.
  • Bean Validation should not replace authorization, domain invariants, external verification, or database constraints.
  • Critical uniqueness and referential rules must be protected by the database.
  • Separate request DTOs are often clearer than complex validation groups.
  • Large object graphs and collections should be bounded to prevent validation-related CPU and memory problems.
  • Senior developers design validation as a layered system covering API structure, business rules, domain invariants, and database integrity.