π‘οΈ Bean Validation: Your Dataβs Personal Bodyguard
Imagine youβre a bouncer at the coolest club in town. Your job? Make sure only the RIGHT people get in. Bean Validation is exactly thatβa bouncer for your data!
π The Big Picture: What is Jakarta Validation?
Think of Jakarta Validation like a spell-checker for your data.
You know how spell-check catches typos before you send an email? Bean Validation catches bad data before it messes up your app!
Real Life Example:
- You fill a form with your email as βbob@β
- Thatβs NOT a real email!
- Bean Validation says: βNope! Fix that first!β β
Why Do We Need It?
// WITHOUT validation - chaos! π₯
user.setAge(-5); // Negative age? Time traveler?
user.setEmail(""); // Empty email? How do we contact them?
// WITH validation - peace! π
@Min(0)
private int age; // Must be 0 or more
@Email @NotEmpty
private String email; // Must be valid email
The Magic: You write rules ONCE. They work EVERYWHERE automatically!
π Built-in Constraints: Ready-Made Rules
Jakarta gives you a toolbox of pre-made rules. No coding needed!
Think of it like LEGO blocks:
Each constraint is a block you snap onto your data.
The Most Popular Ones:
| Constraint | What It Checks | Example |
|---|---|---|
@NotNull |
Not empty | Name required |
@NotEmpty |
Has content | List not empty |
@NotBlank |
Has real text | No blank spaces |
@Size |
Length limit | Password 8-20 chars |
@Min / @Max |
Number range | Age 0-120 |
@Email |
Valid email | Has @ symbol |
@Pattern |
Matches format | Phone number |
@Past / @Future |
Date check | Birthday in past |
See It In Action:
public class User {
@NotBlank(message = "Name needed!")
private String name;
@Email(message = "Invalid email!")
private String email;
@Min(value = 18, message = "Must be adult")
@Max(value = 120, message = "Too old!")
private int age;
@Size(min = 8, max = 20)
private String password;
}
Simple, right? Just add the annotation. Done! β¨
π¨ Custom Constraints: Make Your Own Rules!
Sometimes the built-in rules arenβt enough. Time to create your own!
The Story:
Imagine you run a pizza shop. You need to check if a pizza size is valid: SMALL, MEDIUM, or LARGE.
Thereβs no @PizzaSize built-in. So letβs make one!
Step 1: Create the Annotation
@Constraint(validatedBy = PizzaSizeValidator.class)
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
public @interface ValidPizzaSize {
String message() default "Invalid pizza size!";
Class<?>[] groups() default {};
Class<?>[] payload() default {};
}
Step 2: Create the Validator
public class PizzaSizeValidator
implements ConstraintValidator<ValidPizzaSize, String> {
private static final Set<String> VALID =
Set.of("SMALL", "MEDIUM", "LARGE");
@Override
public boolean isValid(String value,
ConstraintValidatorContext ctx) {
return value != null && VALID.contains(value);
}
}
Step 3: Use It!
public class Pizza {
@ValidPizzaSize
private String size; // Only SMALL, MEDIUM, LARGE
}
You just created your own validation rule! π
π Constraint Composition: Combine Powers!
What if you use the same rules over and over?
The Problem:
// You type this everywhere... boring!
@NotNull
@Size(min = 2, max = 50)
@Pattern(regexp = "^[A-Za-z]+quot;)
private String firstName;
@NotNull
@Size(min = 2, max = 50)
@Pattern(regexp = "^[A-Za-z]+quot;)
private String lastName;
The Solution: Bundle Them!
@NotNull
@Size(min = 2, max = 50)
@Pattern(regexp = "^[A-Za-z]+quot;)
@Constraint(validatedBy = {})
@Target({FIELD})
@Retention(RUNTIME)
public @interface ValidName {
String message() default "Invalid name";
Class<?>[] groups() default {};
Class<?>[] payload() default {};
}
Now Use One Annotation:
public class Person {
@ValidName
private String firstName; // Clean!
@ValidName
private String lastName; // Simple!
}
Think of it like a combo meal πππ₯€βmultiple items, one order!
π₯ Validation Groups: Different Rules for Different Times
The Story:
When you CREATE a user, you donβt need an ID. When you UPDATE a user, you NEED the ID.
Same class, different rules!
Define Groups:
// Just marker interfaces
public interface CreateGroup {}
public interface UpdateGroup {}
Apply Groups:
public class User {
@NotNull(groups = UpdateGroup.class)
private Long id; // Only needed for updates!
@NotBlank(groups = {CreateGroup.class, UpdateGroup.class})
private String name; // Always needed
@Email(groups = CreateGroup.class)
private String email; // Only checked on create
}
Validate With a Group:
// Creating user - skip ID check
validator.validate(user, CreateGroup.class);
// Updating user - check ID
validator.validate(user, UpdateGroup.class);
Same object, different rules! π
π Group Sequences: Order Matters!
Sometimes you want to check things in order.
Why Order Matters:
- Step 1: Is the data formatted correctly?
- Step 2: Is it within limits?
- Step 3: Does it match business rules?
Why check Step 3 if Step 1 fails?
Define Groups:
public interface BasicChecks {}
public interface LimitChecks {}
public interface BusinessChecks {}
Create the Sequence:
@GroupSequence({
BasicChecks.class,
LimitChecks.class,
BusinessChecks.class
})
public interface OrderedValidation {}
Use It:
public class Order {
@NotNull(groups = BasicChecks.class)
private String product;
@Min(value = 1, groups = LimitChecks.class)
@Max(value = 100, groups = LimitChecks.class)
private int quantity;
@ValidStock(groups = BusinessChecks.class)
private String productCode;
}
// Validates in ORDER - stops at first failure!
validator.validate(order, OrderedValidation.class);
Like a checklistβfinish one before moving to next! β
π Class-Level Constraints: Check the Whole Object
Sometimes you need to check multiple fields together.
The Problem:
A date range needs START before END. You canβt check this with single-field annotations!
Create Class-Level Constraint:
@Constraint(validatedBy = DateRangeValidator.class)
@Target(TYPE) // Applied to CLASS!
@Retention(RUNTIME)
public @interface ValidDateRange {
String message() default "End must be after start";
Class<?>[] groups() default {};
Class<?>[] payload() default {};
}
The Validator:
public class DateRangeValidator
implements ConstraintValidator<ValidDateRange, Event> {
@Override
public boolean isValid(Event event,
ConstraintValidatorContext ctx) {
if (event.getStart() == null ||
event.getEnd() == null) {
return true; // Let @NotNull handle nulls
}
return event.getEnd().isAfter(event.getStart());
}
}
Apply to Class:
@ValidDateRange // Checks the WHOLE object!
public class Event {
@NotNull
private LocalDate start;
@NotNull
private LocalDate end;
}
Check relationships between fields! π
π Method Validation: Guard Your Methods!
Validation isnβt just for fields. Methods can have guards too!
Two Places to Check:
βββββββββββββββββββββββββββββββββββββββββββββββ
β YOUR METHOD β
β β
β βββΆ INPUT (Parameters) βββΆ β
β METHOD RUNS β
β βββ OUTPUT (Return Value) βββ β
βββββββββββββββββββββββββββββββββββββββββββββββ
Validate Parameters:
public class Calculator {
public int divide(
@Min(1) int dividend,
@Min(1) int divisor // Can't be zero!
) {
return dividend / divisor;
}
}
Validate Return Values:
public class UserService {
@NotNull // Must return something!
public User findUser(@NotBlank String email) {
return userRepository.findByEmail(email);
}
}
Enable It (CDI/Spring):
@ApplicationScoped
@ValidateOnExecution(type = ExecutableType.ALL)
public class MyService {
// All methods validated automatically!
}
Guard your method doors! πͺ
π― Quick Summary Flow
βββββββββββββββββββββββββββββββββββ
β Jakarta Bean Validation β
βββββββββββββββ¬ββββββββββββββββββββ
β
βββββββββββΌββββββββββ
β Built-in Rules β β @NotNull, @Email, @Size
βββββββββββ¬ββββββββββ
β
βββββββββββΌββββββββββ
β Custom Rules β β Make your own!
βββββββββββ¬ββββββββββ
β
βββββββββββΌββββββββββ
β Compose Rules β β Bundle multiple rules
βββββββββββ¬ββββββββββ
β
βββββββββββΌββββββββββ
β Groups β β Different contexts
βββββββββββ¬ββββββββββ
β
βββββββββββΌββββββββββ
β Sequences β β Ordered checking
βββββββββββ¬ββββββββββ
β
βββββββββββΌββββββββββ
β Class-Level β β Multi-field checks
βββββββββββ¬ββββββββββ
β
βββββββββββΌββββββββββ
β Method Level β β Guard methods
βββββββββββββββββββββββ
π Remember This!
Bean Validation = Data Bouncer
- π Built-in: Use ready-made rules
- π¨ Custom: Create your own rules
- π Compose: Bundle rules together
- π₯ Groups: Different rules for different times
- π Sequences: Check in order
- π Class-Level: Check multiple fields
- π Methods: Guard inputs and outputs
Youβve got the power to keep bad data OUT! πͺ
Next time you build an app, remember: Bad data is like party crashers. Bean Validation is your bouncer. Use it well! π
