Javax Validation
Nested Objects
Bean Validation
Java
Troubleshooting

Javax validation on nested objects - not working

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Javax (Jakarta) Bean Validation does not automatically validate nested objects. If you have an Order with an Address field, adding @NotNull to Address fields does nothing unless the Address field itself is annotated with @Valid. This is the single most common reason nested validation "does not work" — the @Valid annotation is missing on the parent field.

The Problem

java
1public class Order {
2    @NotNull
3    private String orderId;
4
5    @NotNull
6    private Address shippingAddress;  // @NotNull checks if address is null
7                                       // but does NOT validate Address fields
8}
9
10public class Address {
11    @NotBlank
12    private String street;
13
14    @NotBlank
15    private String city;
16
17    @Size(min = 5, max = 5)
18    private String zipCode;
19}

If you submit an Order with a non-null Address that has a blank street, validation passes. The @NotBlank on street is never checked because the validator does not descend into Address.

The Fix: Add @Valid

java
1public class Order {
2    @NotNull
3    private String orderId;
4
5    @Valid        // <-- This triggers validation of Address fields
6    @NotNull
7    private Address shippingAddress;
8}

@Valid tells the validator to recursively validate the annotated object. Now @NotBlank on street and city, and @Size on zipCode are enforced.

@Valid on Collections

@Valid also works on collections to validate each element:

java
1public class Order {
2    @NotNull
3    private String orderId;
4
5    @Valid
6    @NotNull
7    private Address shippingAddress;
8
9    @Valid                          // Validates each LineItem
10    @NotEmpty                      // At least one item required
11    private List<LineItem> items;
12}
13
14public class LineItem {
15    @NotBlank
16    private String productId;
17
18    @Min(1)
19    private int quantity;
20
21    @DecimalMin("0.01")
22    private BigDecimal price;
23}

Without @Valid on the items list, individual LineItem constraints are ignored even if the list contains invalid items.

Spring MVC: @Valid on Controller Parameters

In Spring MVC, you must also use @Valid on the controller method parameter:

java
1@RestController
2@RequestMapping("/orders")
3public class OrderController {
4
5    @PostMapping
6    public ResponseEntity<Order> createOrder(
7            @Valid @RequestBody Order order,   // @Valid triggers validation
8            BindingResult result) {
9
10        if (result.hasErrors()) {
11            // Return validation errors
12            List<String> errors = result.getFieldErrors().stream()
13                .map(e -> e.getField() + ": " + e.getDefaultMessage())
14                .collect(Collectors.toList());
15            return ResponseEntity.badRequest().body(null);
16        }
17
18        return ResponseEntity.ok(orderService.create(order));
19    }
20}

If you omit @Valid from the @RequestBody parameter, no validation runs at all — neither top-level nor nested.

@Validated vs @Valid

Spring provides @Validated as an alternative to @Valid with support for validation groups:

java
1// Validation groups
2public interface OnCreate {}
3public interface OnUpdate {}
4
5public class User {
6    @Null(groups = OnCreate.class)      // Must be null when creating
7    @NotNull(groups = OnUpdate.class)   // Must exist when updating
8    private Long id;
9
10    @NotBlank(groups = {OnCreate.class, OnUpdate.class})
11    private String name;
12
13    @Valid
14    private Address address;
15}
16
17@RestController
18public class UserController {
19
20    @PostMapping("/users")
21    public User create(
22            @Validated(OnCreate.class) @RequestBody User user) {
23        // Only OnCreate constraints are checked
24        return userService.create(user);
25    }
26
27    @PutMapping("/users/{id}")
28    public User update(
29            @Validated(OnUpdate.class) @RequestBody User user) {
30        // Only OnUpdate constraints are checked
31        return userService.update(user);
32    }
33}

@Valid does not support groups — use @Validated when you need group-based validation.

Deeply Nested Objects

@Valid cascades recursively. If Address contains a Country object, add @Valid at each level:

java
1public class Order {
2    @Valid @NotNull
3    private Address shippingAddress;
4}
5
6public class Address {
7    @NotBlank
8    private String street;
9
10    @Valid @NotNull       // Cascades into Country
11    private Country country;
12}
13
14public class Country {
15    @NotBlank
16    private String code;
17
18    @NotBlank
19    private String name;
20}

If you forget @Valid on country, the Country constraints are skipped.

Programmatic Validation

You can also validate objects manually using Validator:

java
1import javax.validation.Validator;
2import javax.validation.ValidatorFactory;
3import javax.validation.Validation;
4import javax.validation.ConstraintViolation;
5
6ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
7Validator validator = factory.getValidator();
8
9Order order = new Order();
10order.setOrderId("ORD-123");
11order.setShippingAddress(new Address());  // Empty address
12
13Set<ConstraintViolation<Order>> violations = validator.validate(order);
14
15for (ConstraintViolation<Order> v : violations) {
16    System.out.println(v.getPropertyPath() + ": " + v.getMessage());
17}
18// Output:
19// shippingAddress.street: must not be blank
20// shippingAddress.city: must not be blank
21// shippingAddress.zipCode: size must be between 5 and 5

This only works if @Valid is on the shippingAddress field.

Dependency Requirements

Ensure you have the validation implementation on the classpath:

xml
1<!-- Spring Boot (includes Hibernate Validator) -->
2<dependency>
3    <groupId>org.springframework.boot</groupId>
4    <artifactId>spring-boot-starter-validation</artifactId>
5</dependency>
6
7<!-- Or standalone Hibernate Validator -->
8<dependency>
9    <groupId>org.hibernate.validator</groupId>
10    <artifactId>hibernate-validator</artifactId>
11    <version>8.0.1.Final</version>
12</dependency>

In Spring Boot 2.3+, spring-boot-starter-web no longer includes validation automatically. You must add spring-boot-starter-validation explicitly.

Jakarta vs Javax Namespace

Starting with Jakarta EE 9, the package changed from javax.validation to jakarta.validation:

java
1// Jakarta EE 9+ (Spring Boot 3.x)
2import jakarta.validation.Valid;
3import jakarta.validation.constraints.NotNull;
4
5// Javax (Spring Boot 2.x, Java EE)
6import javax.validation.Valid;
7import javax.validation.constraints.NotNull;

If your annotations come from the wrong namespace, the validator ignores them entirely.

Common Pitfalls

  • Missing @Valid on the nested field: The number one cause. Without @Valid, the validator skips nested object constraints entirely.
  • Missing @Valid on the controller parameter: In Spring MVC, the @RequestBody parameter must be annotated with @Valid or @Validated to trigger validation.
  • Missing spring-boot-starter-validation: Since Spring Boot 2.3, validation is not included in spring-boot-starter-web. Add the validation starter explicitly.
  • Wrong namespace (javax vs jakarta): Mixing javax.validation annotations with a Jakarta validator (or vice versa) causes silent validation failure.
  • @Valid on a null field: @Valid on a null field does not trigger validation errors for the nested object's fields — it simply skips validation. Combine with @NotNull to ensure the object exists.

Summary

  • Add @Valid on nested object fields to enable cascading validation
  • @Valid works on single objects, collections, and maps
  • In Spring MVC, annotate @RequestBody parameters with @Valid or @Validated
  • Use @Validated (Spring-specific) for validation groups
  • Add spring-boot-starter-validation in Spring Boot 2.3+
  • Check the namespace: javax.validation for Java EE / Spring Boot 2.x, jakarta.validation for Jakarta EE / Spring Boot 3.x

Course illustration
Course illustration

All Rights Reserved.