Validation Error Handling in Jakarta EE
🎠The Story of the Friendly Gatekeeper
Imagine a theme park entrance. Before anyone can enter, a friendly gatekeeper checks their tickets. If something’s wrong—ticket expired, wrong date, or missing name—the gatekeeper doesn’t just say “NO!” and slam the door.
Instead, they kindly explain what’s wrong and how to fix it.
That’s exactly what Validation Error Handling does in Jakarta EE!
🎯 What You’ll Learn
- Constraint Violation Handling — Catching and processing validation errors
- Custom Validation Messages — Making error messages friendly and helpful
🎪 Part 1: Constraint Violation Handling
What is a Constraint Violation?
Think of constraints as rules. Like:
- “You must be at least 5 years old to ride”
- “Your name cannot be empty”
- “Password must have 8 characters”
When someone breaks a rule, that’s a constraint violation.
The ConstraintViolation Object
When validation fails, Jakarta EE gives you a ConstraintViolation object. It’s like a report card that tells you:
| Information | What It Means |
|---|---|
getMessage() |
What went wrong |
getPropertyPath() |
Which field failed |
getInvalidValue() |
The bad value entered |
getRootBean() |
The object being checked |
Simple Example: Catching Violations
// Create a validator
Validator validator = Validation
.buildDefaultValidatorFactory()
.getValidator();
// Check a user object
Set<ConstraintViolation<User>> violations =
validator.validate(user);
// Did anything fail?
if (!violations.isEmpty()) {
for (ConstraintViolation<User> v : violations) {
System.out.println("Error: " + v.getMessage());
System.out.println("Field: " + v.getPropertyPath());
}
}
🎨 How It Works (Visual Flow)
graph TD A["User enters data"] --> B["Validator checks rules"] B --> C{Any violations?} C -->|Yes| D["Get violation details"] C -->|No| E["Data is valid!"] D --> F["Show friendly error"] F --> A
🏠Real-World Example: Registration Form
Let’s say someone tries to register with:
- Name: “” (empty!)
- Age: 5 (too young!)
- Email: “notanemail” (invalid!)
Here’s how we catch ALL these errors:
public class User {
@NotBlank(message = "Name is required")
private String name;
@Min(value = 13, message = "Must be 13+")
private int age;
@Email(message = "Invalid email format")
private String email;
}
// Validate and collect ALL errors
Set<ConstraintViolation<User>> errors =
validator.validate(newUser);
List<String> messages = errors.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toList());
// messages = ["Name is required",
// "Must be 13+",
// "Invalid email format"]
🎨 Part 2: Custom Validation Messages
Why Custom Messages Matter
Default messages are technical and scary:
❌ “must not be null” ❌ “size must be between 1 and 50”
Friendly messages help users:
✅ “Please enter your name” ✅ “Name should be 1-50 characters”
Three Ways to Customize Messages
Way 1: Inline Messages (Simple)
Put the message right in the annotation:
@NotNull(message = "Please enter your email")
private String email;
@Size(min = 8, message = "Password needs 8+ chars")
private String password;
Way 2: Message Properties File (Organized)
Create ValidationMessages.properties:
user.name.required=Please enter your name
user.email.invalid=That email looks wrong
user.age.minimum=You must be at least {min} years old
Then use it:
@NotBlank(message = "{user.name.required}")
private String name;
@Min(value = 13, message = "{user.age.minimum}")
private int age;
Notice {min}? It gets replaced with 13 automatically!
Way 3: Dynamic Parameters
You can include the actual values in messages:
@Size(min = 2, max = 50,
message = "Name must be {min}-{max} characters")
private String name;
// Output: "Name must be 2-50 characters"
🎠The Complete Picture
graph TD A["Define Constraints"] --> B["Add Custom Messages"] B --> C["Validate Object"] C --> D["Get Violations"] D --> E["Extract Messages"] E --> F["Show to User"] F --> G["User Fixes Errors"] G --> C
đź’ˇ Pro Tips
Tip 1: Group Related Errors
Map<String, String> errorMap = violations.stream()
.collect(Collectors.toMap(
v -> v.getPropertyPath().toString(),
v -> v.getMessage()
));
// {"name": "Required", "age": "Must be 13+"}
Tip 2: Handle in REST Endpoints
@POST
public Response create(@Valid User user) {
// Jakarta EE auto-validates!
// If invalid, throws ConstraintViolationException
return Response.ok(user).build();
}
@ExceptionHandler(ConstraintViolationException.class)
public Response handleError(ConstraintViolationException e) {
List<String> errors = e.getConstraintViolations()
.stream()
.map(v -> v.getMessage())
.collect(Collectors.toList());
return Response.status(400).entity(errors).build();
}
Tip 3: Internationalization (i18n)
Create multiple message files:
ValidationMessages.properties (default)
ValidationMessages_es.properties (Spanish)
ValidationMessages_fr.properties (French)
Jakarta EE picks the right one based on user’s locale!
🎯 Quick Summary
| Concept | What It Does |
|---|---|
| ConstraintViolation | Holds error details |
getMessage() |
Gets the error message |
getPropertyPath() |
Gets the field name |
getInvalidValue() |
Gets what user entered |
| Custom Messages | Makes errors friendly |
| {parameters} | Adds dynamic values |
| Properties Files | Organizes messages |
🚀 You Did It!
You now understand how to:
- âś… Catch validation errors using
ConstraintViolation - âś… Extract useful information (message, field, value)
- âś… Create friendly messages inline or in files
- âś… Use dynamic parameters like
{min}and{max}
Think of yourself as that friendly gatekeeper now. You don’t just block bad data—you help users fix it with clear, kind messages!
Remember: Good error messages turn frustrated users into happy users! 🎉
