Of course! Java validation is a crucial topic for building robust, reliable, and user-friendly applications. It involves checking that data conforms to a specific set of rules before it's processed or persisted.

Here’s a comprehensive guide covering the modern, standard approach, along with other common methods.
The Modern Standard: Bean Validation (JSR 380)
This is the recommended and most powerful way to perform validation in modern Java applications (both standard Java EE and Spring). It's a specification, and the most common implementation is Hibernate Validator.
Key Concepts:
- JSR 380: The Java Specification Request that defines the API for Java Bean Validation.
- Bean Validation API (
javax.validation): The standard API for defining validation constraints (e.g.,@NotNull,@Size). - Hibernate Validator: The de-facto reference implementation of the Bean Validation API.
- Constraint: An annotation that you place on a field or method parameter to define a validation rule (e.g.,
@Email). - Validator: The engine that validates an object against its constraints.
How to Use Bean Validation
Step 1: Add Dependencies

You need the Bean Validation API and an implementation like Hibernate Validator. If you're using Spring Boot, this is usually auto-configured for you. For a standard Maven project:
<!-- Bean Validation API -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.2</version>
</dependency>
<!-- Hibernate Validator Implementation -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>
(Note: For older Java EE 8 projects, you would use javax.validation instead of jakarta.validation)
Step 2: Define Constraints on Your Model (POJO)
Annotate your fields with constraint annotations.

import jakarta.validation.constraints.*;
import java.time.LocalDate;
public class User {
@NotBlank(message = "Username cannot be blank.")
@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters.")
private String username;
@NotBlank
@Email(message = "Email should be valid.")
private String email;
@Min(value = 18, message = "You must be at least 18 years old.")
private int age;
@Past(message = "Date of birth must be in the past.")
private LocalDate dateOfBirth;
@Pattern(regexp = "^[0-9]{10}$", message = "Phone number must be 10 digits.")
private String phoneNumber;
// Getters and Setters...
}
Step 3: Perform the Validation
You can use the Validator API directly or, more commonly, rely on framework integrations (like Spring's @Valid).
Method 1: Using the Validator API Directly
import jakarta.validation.Validator;
import java.util.Set;
import jakarta.validation.ConstraintViolation;
public class Main {
public static void main(String[] args) {
// Create a Validator instance (this is usually done via dependency injection)
Validator validator = jakarta.validation.Validation.buildDefaultValidatorFactory().getValidator();
User user = new User();
user.setUsername("ab"); // Too short
user.setEmail("invalid-email"); // Invalid format
user.setAge(16); // Too young
user.setDateOfBirth(LocalDate.now().plusDays(1)); // In the future
user.setPhoneNumber("123"); // Not 10 digits
// Validate the object
Set<ConstraintViolation<User>> violations = validator.validate(user);
// Print the validation errors
if (!violations.isEmpty()) {
System.out.println("Validation failed:");
for (ConstraintViolation<User> violation : violations) {
// violation.getPropertyPath() -> which field failed
// violation.getMessage() -> the error message
System.out.println(String.format("Field: %s, Error: %s",
violation.getPropertyPath(),
violation.getMessage()));
}
} else {
System.out.println("Validation successful!");
}
}
}
Output:
Validation failed:
Field: username, Error: Username must be between 3 and 50 characters.
Field: email, Error: Email should be valid.
Field: age, Error: You must be at least 18 years old.
Field: dateOfBirth, Error: Date of birth must be in the past.
Field: phoneNumber, Error: Phone number must be 10 digits.
Method 2: The Most Common Use Case - Spring MVC Integration
In a Spring Boot application, you use the @Valid annotation on the method parameter to trigger validation automatically. Spring will then handle the errors and populate a BindingResult object.
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api/users")
public class UserController {
@PostMapping
public String createUser(@Valid @RequestBody User user, BindingResult result) {
if (result.hasErrors()) {
// Build a list of error messages
List<String> errors = result.getFieldErrors().stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.toList());
return "Validation failed: " + errors;
}
// If validation passes, proceed with business logic
return "User created successfully: " + user.getUsername();
}
}
If you send an invalid JSON to /api/users, Spring will automatically return a 400 Bad Request with a detailed error response (if @Validated is on the class).
Custom Validation Constraints
Sometimes you need a validation rule that doesn't exist out of the box (e.g., "password must contain a number").
Step 1: Create the Annotation
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Documented
@Constraint(validatedBy = PasswordValidator.class) // Link to the validator class
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidPassword {
String message() default "Password must contain at least one digit, one lowercase, one uppercase, and one special character.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Step 2: Create the Validator Class
This class implements the logic for your custom constraint.
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class PasswordValidator implements ConstraintValidator<ValidPassword, String> {
@Override
public boolean isValid(String password, ConstraintValidatorContext context) {
if (password == null) {
return false; // Or true if null is allowed and you're using @NotNull separately
}
// Regex for at least one digit, one lowercase, one uppercase, and one special character
String regex = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=!])(?=\\S+$).{8,}$";
return password.matches(regex);
}
}
Step 3: Use Your Custom Annotation
public class UserRegistrationDto {
// ... other fields
@ValidPassword
private String password;
// Getters and Setters...
}
Other Validation Methods (Less Common Today)
While Bean Validation is the standard, it's good to know about other approaches.
a. Manual Validation (If/Else Blocks)
This is the simplest but most tedious and error-prone method.
public void createUser(User user) {
if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {
throw new IllegalArgumentException("Username cannot be blank.");
}
if (user.getUsername().length() < 3 || user.getUsername().length() > 50) {
throw new IllegalArgumentException("Username must be between 3 and 50 characters.");
}
if (user.getEmail() == null || !user.getEmail().contains("@")) {
throw new IllegalArgumentException("Email is not valid.");
}
// ... more checks
// If all checks pass, proceed
}
Disadvantages: Code is repetitive, hard to maintain, and validation logic is scattered.
b. Apache Commons Validator
A library that provides reusable validation rules. It's more flexible than Bean Validation in some ways but less integrated with the Java ecosystem.
Example:
import org.apache.commons.validator.routines.EmailValidator;
EmailValidator validator = EmailValidator.getInstance();
if (!validator.isValid("some-email")) {
// Handle invalid email
}
Disadvantages: Not as type-safe as Bean Validation (often uses strings for field names), less integrated with frameworks like Spring.
Summary and Best Practices
| Method | Pros | Cons | When to Use |
|---|---|---|---|
| Bean Validation (JSR 380) | Standard, Declarative, Powerful, Framework Integration (Spring), Customizable | Learning curve for custom validators | The default choice for almost all modern Java applications. |
| Manual (If/Else) | Simple for very basic, one-off checks | Repetitive, hard to maintain, scattered logic | Quick prototypes or extremely simple validation with one or two rules. |
| Apache Commons Validator | Flexible, reusable validators | Less type-safe, poor framework integration | Legacy projects or when you need specific validators not in Bean Validation. |
Best Practices:
- Use Bean Validation: Always start with
jakarta.validationannotations. - Separate Concerns: Keep validation logic in your model/DTO classes, not in your service or controller layers.
- Provide Clear Messages: Use the
messageattribute on your constraints to give users helpful feedback. - Use
@Validatedon Spring Components: Annotate your@Serviceor@Controllerclasses with@Validatedto enable method-level validation. - Leverage Framework Features: In Spring, use
@ValidandBindingResultto handle validation errors gracefully in your controllers.
