What is Jakarta Validation?¶
Jakarta Validation (formerly javax.validation → now jakarta.validation) is a specification and API for declaring constraints on object fields, usually using annotations, and validating them automatically.
You write rules like:
public record CreateUserRequest(
@NotBlank @Email String email,
@NotBlank String name,
@Size(min = 8) String password
) {}
And your framework goes:
“Alright, if someone tries to POST a user without an email or with a cat-length password, we refuse politely.”
It’s declarative validation. You’re describing rules. You’re not writing if (email == null || email.isBlank()) throw new ....
That saves you from “spaghetti validation hell” — those ugly forests of ifs that creep into beginner service layers.
Why does it exist?¶
Historically, developers scattered validation logic all over:
- UI input checks
- Controller checks
- Service layer checks
- Database constraints
Each spot duplicated the same rules. Boredom, bugs, sadness.
Jakarta Validation centralizes rules so you can:
- Declare once, validate everywhere
- Keep your business code focused on business logic, not guarding doors
- Get consistent error format for bad input
- Plug in custom validators when life gets fancy
The universe likes DRY code; this is a tool aligned with entropy reduction.
How does Spring hook into it?¶
Spring uses Hibernate Validator under the hood to implement the Jakarta spec. When you put @Valid in your controller method, you're saying:
"Framework, inspect this object, and if it doesn't meet expectations, toss a polite tantrum."
Example:
@PostMapping("/users")
public ResponseEntity<UserResponse> createUser(
@Valid @RequestBody CreateUserRequest req) {
...
}
When validation fails, Spring throws MethodArgumentNotValidException. If you have a global exception handler, you translate that into your structured API error.
Without one, Spring returns its default JSON scolding.
Common constraints you’ll see¶
Human-memory-friendly version first, so your brain absorbs the essence:
- @NotNull — must exist
- @NotBlank — must contain non-space characters
- @Email — must smell like an email
- @Size(min, max) — good for strings & collections
- @Min / @Max — number guardians
- @Pattern — regex wizardry
- @Past / @Future — great for dates (birthday in the future? you’re time-traveling or lying)
Mechanical versions, if your cortex enjoys officiality:
@NotNull
@NotBlank
@NotEmpty
@Email
@Size(min = 3, max = 255)
@Min(1)
@Max(100)
@Positive
@Negative
@PositiveOrZero
@Pattern(regexp="regex here")
@Past
@PastOrPresent
@Future
@FutureOrPresent
You will forget @NotEmpty exists because @NotBlank is nicer for text. This is fine. The world continues spinning.
Beyond simple annotations: custom validators¶
Sometimes reality is annoying, like validating:
- Password strength (uppercase, digit, symbol…)
- "End date must be after start date"
- "Username cannot contain the word 'bot'"
- "VAT number valid for EU country"
You create annotation + validator:
@Target({ FIELD })
@Retention(RUNTIME)
@Constraint(validatedBy = StrongPasswordValidator.class)
public @interface StrongPassword {
String message() default "Weak password";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Implement logic:
public class StrongPasswordValidator implements ConstraintValidator<StrongPassword, String> {
public boolean isValid(String value, ConstraintValidatorContext ctx) {
return value != null
&& value.matches(".*[A-Z].*")
&& value.matches(".*\\d.*")
&& value.length() >= 8;
}
}
These become reusable, clean, and readable.
Just don't get too validator-happy and try encoding your whole business logic there. Validators are for input sanity, not domain decisions.
Where validation belongs in the grand architecture story?¶
Quick conceptual map:
- DTO / Request layer → use Jakarta Validation (describing shape of input)
- Domain layer → enforce invariants (business rules)
- DB → final sanity gates (e.g., unique constraints)
Think of it like concentric shields: client input → request validation → domain invariants → database constraints
Validation does not replace business rules. Your domain still says things like:
"User must be at least 18 to register."
Validation only says:
"Age field must be present and a number."
Future-proof note: javax.validation vs jakarta.validation¶
Modern apps = jakarta.validation. Old tutorials may still show javax.*. Same concept, just a namespace shift when Java EE moved to Eclipse Foundation.
If your IDE screams mismatched imports, it's not angry; it's reminding you time moves forward.
Quick example end-to-end¶
Request DTO:
public record CreateProductRequest(
@NotBlank String name,
@Min(1) int quantity,
@Positive double price
) {}
Controller:
@PostMapping
public ResponseEntity<ProductResponse> create(
@Valid @RequestBody CreateProductRequest req) {
var result = service.create(req);
return ResponseEntity.ok(result);
}
If validation fails, your global handler formats it to ProblemDetail or your flavor of JSON errors.
Clean, predictable, civilized.
Mental model to carry¶
Jakarta Validation is not a silver bullet. It’s a first line of defense, not your whole army. It keeps nonsense out, but meaning is enforced by your business logic.
Same way a gym membership won't make you fit — but it definitely helps.
Where to explore next¶
Once you’re comfortable with validation, interesting paths branch out:
- ProblemDetail + error catalog patterns
- Constraint composition (
@ValidAge @ValidName) - Validation groups (e.g. different rules for CREATE vs UPDATE)
- Record DTOs vs JavaBeans
- Fluent error reporting patterns
- Domain invariants vs input validation boundaries
Validation is just the first guard tower. The castle of backend design keeps going.