Javax Validation
Nested Objects
Bean Validation
Java
Troubleshooting

Javax validation on nested objects - not working

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

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

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.