Bean Validation

Back

Loading concept...

πŸ›‘οΈ 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! πŸŽ‰

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.