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 the User 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

LayerUsage ExampleWhy It Helps
DTO/Input@Valid @RequestBodyValidates user input (HTTP requests)
Entities@NotNull, @Size, etc.Ensures database integrity
Servicesvalidator.validate(obj)Central business rule enforcement
Custom Objects@Valid on nested fieldsRecursive, object graph validation
REST (JAX-RS)@Valid on resource methodsAuto 400 response for invalid inputs

By admin

Leave a Reply

Your email address will not be published. Required fields are marked *