Jakarta Bean Validation (formerly part of Java EE under the name JSR 380 – Bean Validation 2.0) is a powerful and declarative way to enforce validation constraints on Java objects. Whether you’re working on a Spring Boot application, a Jakarta EE project, or a standalone Java program, the jakarta.validation
package offers a clean and extensible way to validate input and domain models.
In this post, we’ll walk through key concepts and annotations in Jakarta Bean Validation, backed with Java code examples for each one.
Setting Up Jakarta Validation
Before diving into examples, ensure you have the right dependencies in your project. If you’re using Maven:
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>7.0.5.Final</version>
</dependency>
A Simple Example: Validating a User
Let’s start with a simple User
class and gradually add constraints.
import jakarta.validation.constraints.*;
public class User {
@NotNull
private String name;
@Email
private String email;
@Min(18)
@Max(100)
private int age;
// Getters and setters
}
Explanation:
@NotNull
: Ensures the name is not null.@Email
: Checks the email format.@Min
and@Max
: Ensure the age is within a valid range.
To validate an object:
import jakarta.validation.*;
import java.util.Set;
public class Main {
public static void main(String[] args) {
User user = new User(); // All fields are null or default
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<User>> violations = validator.validate(user);
for (ConstraintViolation<User> violation : violations) {
System.out.println(violation.getPropertyPath() + ": " + violation.getMessage());
}
}
}
More Useful Constraints
1. @Size
– For Strings, Collections, Arrays
@Size(min = 2, max = 30)
private String username;
Useful when you want to control the length of input (e.g., passwords, usernames).
2. @Pattern
– Regex-based validation
You can use this to enforce complex formats, such as license plates, national IDs, or custom codes.
@Pattern(regexp = "^[A-Z]{2}\\d{4}$")
private String employeeCode; // e.g., "AB1234"
3. @Past
, @Future
, @PastOrPresent
, @FutureOrPresent
Ideal for dealing with date validations in business logic.
@Past
private LocalDate birthDate;
@Future
private LocalDate membershipExpiryDate;
4. @AssertTrue
/ @AssertFalse
These annotations are great for boolean flags like terms of service or subscription options.
@AssertTrue
private boolean acceptedTerms;
5. Nested Validation: @Valid
Let’s say a User has an Address object:
public class Address {
@NotBlank
private String street;
@NotBlank
private String city;
}
Now embed it in User
:
@Valid
private Address address;
Without @Valid, nested validations won’t run automatically!
Custom Constraint: Creating Your Own Annotation
You can define custom constraints with ease:
@Constraint(validatedBy = EvenNumberValidator.class)
@Target({ FIELD })
@Retention(RUNTIME)
public @interface Even {
String message() default "must be even";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And the validator class:
public class EvenNumberValidator implements ConstraintValidator {
public void initialize(Even constraint) {}
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return value != null && value % 2 == 0;
}
}
Use it like this:
@Even
private int luckyNumber;
Groups and Conditional Validation
You can use validation groups to apply different constraints in different contexts (e.g., @CreateGroup
, @UpdateGroup
).
public interface CreateGroup {}
public interface UpdateGroup {}
@NotNull(groups = CreateGroup.class)
private String password;
Using Jakarta Validation in Spring Boot
Spring Boot has first-class support for Jakarta Bean Validation. It automatically integrates with the validation API and provides seamless constraint enforcement in web controllers, service layers, and data binding.
Validating Request Bodies in Spring REST Controllers
Let’s enhance our previous User
class and use it in a Spring Boot controller.
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public ResponseEntity<String> createUser(@Valid @RequestBody User user) {
return ResponseEntity.ok("User is valid: " + user.getName());
}
}
What’s going on?
@Valid
tells Spring to validate theUser
object.- If validation fails, Spring automatically returns a
400 Bad Request
with a detailed error message.
Global Exception Handling (Optional)
To customize how validation errors are returned:
@RestControllerAdvice
public class ValidationExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationErrors(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage()));
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
Validation in Services or DTOs
You can also validate programmatically using the same Validator
interface we saw earlier.
@Service
public class UserService {
@Autowired
private Validator validator;
public void saveUser(User user) {
Set<ConstraintViolation<User>> violations = validator.validate(user);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
// Save user to database
}
}
Using Jakarta Validation with Jakarta REST (JAX-RS)
In Jakarta EE (formerly Java EE), Bean Validation integrates smoothly with JAX-RS for validating REST inputs.
@Path("/users")
public class UserResource {
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.TEXT_PLAIN)
public Response createUser(@Valid User user) {
return Response.ok("User is valid").build();
}
}
With the @Valid annotation, the Jakarta EE runtime validates the request body and responds with a 400 Bad Request if there are violations.
If you want to globally catch validation exceptions:
@Provider
public class ValidationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
@Override
public Response toResponse(ConstraintViolationException ex) {
StringBuilder sb = new StringBuilder();
for (ConstraintViolation<?> violation : ex.getConstraintViolations()) {
sb.append(violation.getPropertyPath()).append(": ").append(violation.getMessage()).append("\n");
}
return Response.status(Response.Status.BAD_REQUEST)
.entity(sb.toString())
.type(MediaType.TEXT_PLAIN)
.build();
}
}
Testing Validation
In both Spring and Jakarta EE, you can write unit tests to ensure constraints behave correctly.
Example (Spring + JUnit):
@SpringBootTest
public class UserValidationTest {
@Autowired
private Validator validator;
@Test
public void testInvalidUser() {
User user = new User(); // Empty user
Set<ConstraintViolation<User>> violations = validator.validate(user);
assertFalse(violations.isEmpty());
}
}
Summary: When and Where to Use Bean Validation
Layer | Usage Example | Why It Helps |
---|---|---|
DTO/Input | @Valid @RequestBody | Validates user input (HTTP requests) |
Entities | @NotNull , @Size , etc. | Ensures database integrity |
Services | validator.validate(obj) | Central business rule enforcement |
Custom Objects | @Valid on nested fields | Recursive, object graph validation |
REST (JAX-RS) | @Valid on resource methods | Auto 400 response for invalid inputs |