Hibernate Validation
Bean Validation (Jakarta Validation)
Hibernate Validator is the reference implementation of Jakarta Bean Validation (formerly javax.validation). It allows you to declare constraints directly on entity fields using annotations. Spring Boot includes it automatically via spring-boot-starter-validation.
package com.example.model;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
@Entity
@Table(name = "users")
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull(message = "Name is required")
@NotBlank(message = "Name cannot be blank")
@Size(min = 2, max = 50, message = "Name must be between 2 and 50 characters")
private String name;
@NotNull(message = "Email is required")
@Email(message = "Email must be a valid email address")
@Column(unique = true)
private String email;
@NotNull(message = "Password is required")
@Size(min = 8, message = "Password must be at least 8 characters")
@Pattern(regexp = "^(?=.*[A-Z])(?=.*[0-9]).+$",
message = "Password must contain at least one uppercase letter and one digit")
private String password;
@Min(value = 18, message = "Age must be at least 18")
@Max(value = 120, message = "Age must be at most 120")
private Integer age;
@DecimalMin(value = "0.0", inclusive = false, message = "Salary must be positive")
@DecimalMax(value = "999999.99", message = "Salary exceeds maximum")
@Digits(integer = 6, fraction = 2, message = "Invalid salary format")
private Double salary;
@NotNull
@Positive(message = "Score must be positive")
private Integer score;
@Past(message = "Birth date must be in the past")
private java.time.LocalDate birthDate;
@Future(message = "Expiry date must be in the future")
private java.time.LocalDate expiryDate;
@AssertTrue(message = "Terms must be accepted")
private boolean termsAccepted;
// getters/setters...
}
Custom Constraint Annotation
package com.example.validation;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
// Step 1: Define the annotation
@Documented
@Constraint(validatedBy = UsernameValidator.class) // Link to validator class
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidUsername {
String message() default "Username must be 3-20 chars, alphanumeric and underscores only";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
// Usage on entity field:
// @ValidUsername
// private String username;
package com.example.validation;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
// Step 2: Implement the validator logic
public class UsernameValidator
implements ConstraintValidator<ValidUsername, String> {
private static final String USERNAME_PATTERN = "^[a-zA-Z0-9_]{3,20}$";
@Override
public void initialize(ValidUsername annotation) {
// Called once before validation — can read annotation attributes here
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) return true; // Let @NotNull handle null check
if (!value.matches(USERNAME_PATTERN)) {
// Optionally customize the error message
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(
"Username '" + value + "' is invalid. Use 3-20 alphanumeric chars.")
.addConstraintViolation();
return false;
}
return true;
}
}
@Valid in Spring MVC Controllers
package com.example.controller;
import com.example.model.User;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.validation.FieldError;
import java.util.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
// @Valid triggers Bean Validation on the @RequestBody
// If validation fails, Spring throws MethodArgumentNotValidException
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
// If we reach here, validation passed
return ResponseEntity.status(201).body(userService.save(user));
}
// Global exception handler for validation errors
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationErrors(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new LinkedHashMap<>();
// Collect all field errors
ex.getBindingResult().getAllErrors().forEach(error -> {
String field = ((FieldError) error).getField();
String message = error.getDefaultMessage();
errors.put(field, message);
});
return ResponseEntity.badRequest().body(errors);
}
}
// Example error response for invalid input:
// {
// "name": "Name must be between 2 and 50 characters",
// "email": "Email must be a valid email address",
// "age": "Age must be at least 18"
// }
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.