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:
@NotNullvalidates the Java object.nullable = falseinfluences schema mapping.- A database
NOT NULLconstraint 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:
- A validation annotation
- A validator class
- Error-message definition
- 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
@NotBlankfor mandatory user text. - Use
@Validfor 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
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
@NotNullwhen@NotBlankis 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
Optionalwith 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.
@NotNullrejects only null values.@NotEmptyrejects null and empty values.@NotBlankrejects null, empty, and whitespace-only text.- Built-in constraints support strings, collections, numbers, dates, booleans, and patterns.
@Validis 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.