Bean Validation (Jakarta Bean Validation)
Validation Library
Bean Validation (Jakarta Bean Validation)
Overview
Jakarta Bean Validation (formerly Java Bean Validation) is the standard validation framework for Java applications. Using an annotation-based approach, it ensures that bean properties meet specific criteria. As part of Jakarta EE and Java SE, it is widely used for enterprise applications and API validation.
Details
Jakarta Bean Validation provides object-level constraint declaration and validation facilities. It allows declarative validation rule definition using annotations like @NotNull, @Min, and @Max. Version 3.0 migrated to the jakarta package namespace, and version 3.1 updated the minimum Java requirement to Java 17.
Key features:
- Standard validation annotations: Rich built-in constraints like @NotNull, @Size, @Min, @Max, @Email, @Pattern
- Custom constraints: Ability to implement custom validation logic
- Group validation: Partial validation and order control
- Method validation: Parameter and return value validation (since 1.1)
- Cascading: Recursive validation of related objects with @Valid
- Container element validation: Type argument validation like List<@Valid Employee> (since 2.0)
Hibernate Validator serves as the reference implementation and integrates seamlessly with Spring Boot and Jakarta EE containers.
Advantages and Disadvantages
Advantages
- Standardization: Industry standard as Java EE/Jakarta EE specification
- Declarative approach: Clean and readable code through annotations
- Integration: Excellent integration with major frameworks like Spring Boot, JAX-RS, JPA
- Extensibility: Easy creation of custom validators
- Internationalization: Multi-language support for error messages
- Performance: Efficient validation execution
Disadvantages
- Learning curve: Advanced features like group validation require time to understand
- Annotation dependency: Excessive use can make code verbose
- Complex validation: Complex business logic requires more custom validators
- Version migration: Migration work needed from javax to jakarta namespace (Spring Boot 3+)
Reference Pages
- Jakarta Validation Official Site
- Jakarta Bean Validation Specification
- Hibernate Validator Reference Guide
- Spring Boot Validation
- Jakarta EE Tutorial
Code Examples
Basic Validation
import jakarta.validation.constraints.*;
public class User {
@NotNull(message = "Name is required")
@Size(min = 2, max = 50, message = "Name must be between 2 and 50 characters")
private String name;
@NotNull(message = "Email is required")
@Email(message = "Please provide a valid email address")
private String email;
@Min(value = 18, message = "Age must be at least 18")
@Max(value = 120, message = "Age must not exceed 120")
private int age;
@Pattern(regexp = "^\\d{5}(-\\d{4})?$",
message = "ZIP code must be in format XXXXX or XXXXX-XXXX")
private String zipCode;
// getters and setters
}
Creating Custom Validators
// Custom annotation
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
@Documented
public @interface PhoneNumber {
String message() default "Invalid phone number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String countryCode() default "US";
}
// Validator implementation
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {
private String countryCode;
@Override
public void initialize(PhoneNumber annotation) {
this.countryCode = annotation.countryCode();
}
@Override
public boolean isValid(String phoneNumber, ConstraintValidatorContext context) {
if (phoneNumber == null) {
return true; // Use with @NotNull
}
// Check US phone number format
if ("US".equals(countryCode)) {
return phoneNumber.matches("^\\+?1?[\\s-]?\\(?\\d{3}\\)?[\\s-]?\\d{3}[\\s-]?\\d{4}$");
}
return false;
}
}
Group Validation
// Define validation groups
public interface BasicInfo {}
public interface AdvancedInfo {}
public class Employee {
@NotNull(groups = BasicInfo.class)
private String name;
@NotNull(groups = BasicInfo.class)
@Email(groups = BasicInfo.class)
private String email;
@NotNull(groups = AdvancedInfo.class)
@Size(min = 10, max = 10, groups = AdvancedInfo.class)
private String employeeId;
@Min(value = 0, groups = AdvancedInfo.class)
@Max(value = 1000000, groups = AdvancedInfo.class)
private BigDecimal salary;
}
// Usage example
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
// Validate basic info only
Set<ConstraintViolation<Employee>> basicViolations =
validator.validate(employee, BasicInfo.class);
// Validate advanced info only
Set<ConstraintViolation<Employee>> advancedViolations =
validator.validate(employee, AdvancedInfo.class);
// Validate all
Set<ConstraintViolation<Employee>> allViolations =
validator.validate(employee, BasicInfo.class, AdvancedInfo.class);
Cascade Validation
public class Order {
@NotNull
private String orderId;
@NotNull
@Valid // Enable cascade validation
private Customer customer;
@NotEmpty
@Valid // Validate list elements
private List<@Valid OrderItem> items;
@Valid
private Map<@NotNull String, @Valid Product> productMap;
}
public class Customer {
@NotNull
@Size(min = 2, max = 100)
private String name;
@NotNull
@Valid
private Address address;
}
public class Address {
@NotBlank
private String street;
@NotBlank
@Pattern(regexp = "\\d{5}(-\\d{4})?")
private String zipCode;
}
Spring Boot Integration
// Usage in REST Controller
@RestController
@RequestMapping("/api/users")
@Validated // Enable validation at class level
public class UserController {
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
// Validation is automatically performed with @Valid
// Returns 400 Bad Request on validation errors
return ResponseEntity.ok(userService.save(user));
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(
@PathVariable Long id,
@Valid @RequestBody User user) {
return ResponseEntity.ok(userService.update(id, user));
}
}
// Error handling
@RestControllerAdvice
public class ValidationExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return ResponseEntity.badRequest().body(errors);
}
}
Method Validation
@Service
@Validated // Enable method validation
public class PaymentService {
public void processPayment(
@NotNull @Valid CreditCard card,
@Min(1) @Max(1000000) BigDecimal amount) {
// Method parameters are automatically validated
}
@NotNull
@Valid
public Receipt completeTransaction(
@NotNull String transactionId) {
// Return values can also be validated
return new Receipt();
}
}
// Configuration (auto-configured in Spring Boot)
@Configuration
@EnableMethodValidation
public class ValidationConfig {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}