@Valid annotation is not validating the list of child objects

JavaSpringSpring MvcJsr

Java Problem Overview


Main model classes are as follows :

public class UserAddressesForm {

	@NotEmpty
	private String firstName;

	@NotEmpty
	private String lastName;

	private List<AddressForm> addresses;

    // setters and getters 

}

public class AddressForm {
	
	@NotEmpty
	private String customName;
	@NotEmpty
	private String city;
	@NotEmpty
	private String streetAn;
	@NotEmpty
	private String streetHn;
	@NotEmpty
	private String addressCountry;
	@NotEmpty
	private String postCode;
    
    // setters and getters
}

An endpoint in one of my controllers :

@RequestMapping(value = "/up", method = RequestMethod.POST)
public String completeForm(@Valid @ModelAttribute("userAddressesForm") UserAddressesForm userAddressesForm,  
			BindingResult result, HttpServletRequest req) {

 // logic here 

}

A .jsp page :

<form:form commandName="userAddressesForm" action="registered">
	<table>

		<tr>
			<td class="formLabels"><form:label path="firstName">
				<spring:message code="label.name" />
			</form:label></td>
			<td><form:input path="firstName" /></td>
			<td><form:errors path="firstName" cssClass="error" /></td>
		</tr>
		<tr>
			<td class="formLabels"><form:label path="lastName">
				<spring:message code="label.surname" />
			</form:label></td>
			<td><form:input path="lastName" /></td>
			<td><form:errors path="lastName" cssClass="error" /></td>
		</tr>
	</table>
	
	<c:forEach items="${userAddressesForm.addresses}" varStatus="gridRow">	
		<div id="main_address" class="address_data_form">
			<fieldset>
				<legend><spring:message code="label.stepThreeMainAddressInfo" /></legend>
				<a href="#" class="deleteItem"></a>
				<table>
					<tr>			
						<td class="formLabels">
							<spring:message code="label.address.custom.name" />
						</td>
						<td>
							<spring:bind path="addresses[${gridRow.index}].customName">
								<input type="input" name="<c:out value="${status.expression}"/>"
									id="<c:out value="${status.expression}"/>"
									value="<c:out value="${status.value}"/>" />
									<form:errors path="${status.expression}"/>
							</spring:bind>
						</td>	
					</tr>				
					<tr>			
						<td class="formLabels">
							<spring:message code="label.streetAnStreetHn" />
						</td>
						<td>
							<spring:bind path="addresses[${gridRow.index}].streetAn">
								<input type="input" name="<c:out value="${status.expression}"/>"
									id="<c:out value="${status.expression}"/>"
									value="<c:out value="${status.value}"/>" />
							</spring:bind>
							<spring:bind path="addresses[${gridRow.index}].streetHn">
							<input type="input" name="<c:out value="${status.expression}"/>"
								id="<c:out value="${status.expression}"/>"
								value="<c:out value="${status.value}"/>" >
							<form:errors path="addresses[${gridRow.index}].streetHn"/>
							</spring:bind>
							
						</td>
					</tr>
					<tr>						
						<td class="formLabels">
							<spring:message code="label.postCode" />
						</td>
						<td>
							<spring:bind path="addresses[${gridRow.index}].postCode">
								<input type="input" name="<c:out value="${status.expression}"/>"
									id="<c:out value="${status.expression}"/>"
									value="<c:out value="${status.value}"/>" />
							</spring:bind>
						</td>					
					</tr>
					<tr>				
						<td class="formLabels">
							<spring:message code="label.city" />
						</td>
						<td>
							<spring:bind path="addresses[${gridRow.index}].city">
								<input type="input" name="<c:out value="${status.expression}"/>"
									id="<c:out value="${status.expression}"/>"
									value="<c:out value="${status.value}"/>" />
								<form:errors path="addresses[${gridRow.index}].city" cssClass="error" />
							</spring:bind>
						</td>
					</tr>		
				</table>	
			</fieldset>
		</div>
	</c:forEach>

Why @Valid is not validating the List<AddressForm> addresses present in UserAddressesForm class ?

Java Solutions


Solution 1 - Java

You need to decorate addresses member of UserAddressesForm with @Valid annotation. See section 3.1.3 and 3.5.1 of JSR 303: Bean Validation. As I explained in my answer to the question Is there a standard way to enable JSR 303 Bean Validation using annotated method, this is the real use of @Valid annotation as per JSR 303.

Edit Example code: Hibernate Validator- Object Graph. (The list of passengers in Car)

Edit From Hibernate Validator 6 Reference doc:

> In versions prior to 6, Hibernate Validator supported cascaded validation for a subset of container elements and it was implemented at the container level (e.g. you would use @Valid private List<Person> to enable cascaded validation for Person). > > This is still supported but is not recommended. Please use container > element level @Valid annotations instead as it is more expressive.

Example:

public class Car {
    
        private List<@NotNull @Valid Person> passengers = new ArrayList<Person>();
   
        private Map<@Valid Part, List<@Valid Manufacturer>> partManufacturers = new HashMap<>();
   
       //...
   }

Also see what's new in Bean Validation 2.0/Jakarta Bean Validation.

Solution 2 - Java

Adding to @Ritesh answer, @Valid constraint will instruct the Bean Validator to delve to the type of its applied property and validate all constraints found there. Answer with code to your question, the validator, when seeing a @Valid constraint on addresses property, will explore the AddressForm class and validate all JSR 303 constraints found inside, as follows:

public class UserAddressesForm {

    @NotEmpty
    private String firstName;

    @NotEmpty
    private String lastName;

    @Valid
    private List<AddressForm> addresses;

...
setters and getters 

public class AddressForm {

    @NotEmpty
    private String customName;
    @NotEmpty
    private String city;
    @NotEmpty
    private String streetAn;
    @NotEmpty
    private String streetHn;
    @NotEmpty
    private String addressCountry;
    @NotEmpty
    private String postCode;
...
setters and getters

Solution 3 - Java

In the class UserAddressesForm add the following lines

@Valid
private List<AddressForm> addresses;

Solution 4 - Java

Working solution.

public class UserAddressesForm {

    @NotEmpty(message="firstName is required")
    private String firstName;

    @NotEmpty(message="lastNameis required")
    private String lastName;

    @NotNull(message="addresses attributes are required")
    @Valid
    private List<AddressForm> addresses;

...
setters and getters 

public class AddressForm {

    @NotEmpty(message="customNameis required")
    private String customName;
    @NotEmpty
    private String city;
    @NotEmpty
    private String streetAn;
    @NotEmpty
    private String streetHn;
    @NotEmpty
    private String addressCountry;
    @NotEmpty
    private String postCode;
...
setters and getters

Solution 5 - Java

I implemented all the suggestions and annotated child field in the parent class with with @Valid but still the child entities were not getting validated.

Luckily it is resolved now and the issue was because of the dependency. As a solution I used below dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

instead of

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
</dependency>

Solution 6 - Java

For me, it was @NotNull, in the child, and not a @NotEmpty annotation. I had to put a @NotNull before the @Valid annontation to make it work.

like this:

private List<@NotNull @Valid YourObject> yourObjects;

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questiontomasz-merView Question on Stackoverflow
Solution 1 - JavaRiteshView Answer on Stackoverflow
Solution 2 - JavaArpit AggarwalView Answer on Stackoverflow
Solution 3 - JavaTapan BankerView Answer on Stackoverflow
Solution 4 - JavaWaqas AhmedView Answer on Stackoverflow
Solution 5 - JavaSingla DeepanshuView Answer on Stackoverflow
Solution 6 - JavaNodi View Answer on Stackoverflow