Validation of a List of Objects in Spring

Spring MVC - @Valid on list of beans in REST service

@Valid is a JSR-303 annotation and JSR-303 applies to validation on JavaBeans. A java.util.List is not a JavaBean (according to the official description of a JavaBean), hence it cannot be validated directly using a JSR-303 compliant validator. This is supported by two observations.

Section 3.1.3 of the JSR-303 Specification says that:

In addition to supporting instance validation, validation of graphs of object is also supported. The result of a graph validation is returned as a unified set of constraint violations. Consider the situation where bean X contains a field of type Y. By annotating field Y with the @Valid annotation, the Validator will validate Y (and its properties) when X is validated. The exact type Z of the value contained in the field declared of type Y (subclass, implementation) is determined at runtime. The constraint definitions of Z are used. This ensures proper polymorphic behavior for associations marked @Valid.

Collection-valued, array-valued and generally Iterable fields and properties may also be decorated with the @Valid annotation. This causes the contents of the iterator to be validated. Any object implementing java.lang.Iterable is supported.

I have marked the important pieces of information in bold. This section implies that in order for a collection type to be validated, it must be encapsulated inside a bean (implied by Consider the situation where bean X contains a field of type Y); and further that collections cannot be validated directly (implied by Collection-valued, array-valued and generally Iterable fields and properties may also be decorated, with emphasis on fields and properties).

Actual JSR-303 implementations

I have a sample application that tests collection validation with both Hibernate Validator and Apache Beans Validator. If you run tests on this sample as mvn clean test -Phibernate (with Hibernate Validator) and mvn clean test -Papache (for Beans Validator), both refuse to validate collections directly, which seems to be in line with the specification. Since Hibernate Validator is the reference implementation for JSR-303, this sample is further proof that collections need to be encapsulated in a bean in order to be validated.


With that cleared, I would say that there is also a design problem in trying to pass a collection to a controller method directly in the way shown in the question. Even if validations were to work on the collections directly, the controller method will be unable to work with alternate data representations such as custom XML, SOAP, ATOM, EDI, Google Protocol Buffers etc. which do not map directly to collections. For supporting those representations, the controller must accept and return object instances. That would require encapsulating the collection inside an object instance any way. It would therefore be highly advisable to wrap the List inside another object as other answers have suggested.

Spring boot, how to use @Valid with ListT

My immediate suggestion is to wrap the List in another POJO bean. And use that as the request body parameter.

In your example.

@RequestMapping(value="/bulk", method = RequestMethod.POST)
public List<DataResponse> bulkAdd(@RequestBody @Valid StatusList statusList, BindingResult bindingResult) {
// some code here
}

and StatusList.java will be

@Valid
private List<Status> statuses;
//Getter //Setter //Constructors

I did not try it though.

Update:
The accepted answer in this SO link gives a good explanation why bean validation are not supported on Lists.

Validate a list of nested objects with Spring validator?

public class MyFormValidator implements Validator {

@Override
public boolean supports(Class clazz) {
return MyForm.class.equals(clazz);
}

@Override
public void validate(Object target, Errors errors) {
MyForm myForm = (MyForm) target;

for (int i = 0; i < myForm.getListObjects().size(); i++) {
TypeA typeA = myForm.getListObjects().get(i);

if(typeAHasAnErrorOnNumber) {
errors.rejectValue("listObjects[" + i + "].number", "your_error_code");
}

...
}

...
}

}

Interesting links :

  • Spring MVC: Multiple Row Form Submit using List of Beans

Validate ListObject using Spring validator

org.springframework.validation.Validator cannot validate List<Object> as a target. You must wrap the List in a single Object with getter to let validator to find a path to access sub objects.

public class AllergyDtoListValidator implements Validator {

private AllergyDtoValidator allergyDtoValidator;

@Override
public boolean supports(Class<?> clazz) {
return AllergyDtoList.class.isAssignableFrom(clazz);
}

@Override
public void validate(Object target, Errors errors) {
allergyDtoValidator = new AllergyDtoValidator();
AllergyDtoList request = (AllergyDtoList) target;
for (int i = 0; i < request.size(); i++) {
// sub path name must be same as property name to enable Validator to use
// getter in the object AllergyDtoList
errors.pushNestedPath("allergies["+i+"]");
ValidationUtils.invokeValidator(this.allergyDtoValidator, request.get(i), errors);
errors.popNestedPath();
}
}
}

// new wrapping object to be validated
class AllergyDtoList {

List<AllergyDto> allergies;
}

Spring Boot List of Object Bean Validation

The @Valid annotation in your controller triggers the validation of the PersonRequest object, passed as request body. To validate also the Person objects contained in PersonRequest, you need to annotate that field with @Valid too.

@Data
@NoArgsConstructor
public final class PersonRequest {

@NotNull
@JsonProperty("nameList")
@Valid
private List<Person> nameList;
}

Spring list validation - get field from invalid object

You can access information about objects that violated the constraint by unwrapping your ObjectError or FieldError to ConstraintViolation (https://docs.oracle.com/javaee/7/api/javax/validation/ConstraintViolation.html).

Here is how you can access array index

Path nodes = error.unwrap(ConstraintViolation.class).getPropertyPath();
for (Path.Node node : nodes) {
if (node.isInIterable()) {
node.getIndex() // for items[12346].surname it will return 123456
}
}

Accessing an element that has thrown an error is also possible using unwrap to ConstraintViolation.

Item item = (Item) error.unwrap(ConstraintViolation.class).getLeafBean(); // don't use cast with no checks in your code

This will return an item which triggered constraint violation.

How to validate each object in the List in spring controller parameter without wrapping the List in other class?

Not sure if it's the only, or the best solution, but you can use a wrapper object, without having to change the JSON, using the @JsonValue and @JsonCreator annotations. Here is a complete example:

public class BulkDTOWrapper {

private List<BulkDTO> bulks;

@JsonCreator
public BulkDTOWrapper(List<BulkDTO> bulks) {
this.bulks = bulks;
}

public BulkDTOWrapper() {
}

@JsonValue
public List<BulkDTO> getBulks() {
return bulks;
}

public void setBulks(List<BulkDTO> bulks) {
this.bulks = bulks;
}

public static void main(String[] args) throws IOException {
BulkDTO b1 = new BulkDTO("hello");
BulkDTO b2 = new BulkDTO("world");

BulkDTOWrapper wrapper = new BulkDTOWrapper();
wrapper.setBulks(Arrays.asList(b1, b2));

ObjectMapper om = new ObjectMapper();
String json = om.writeValueAsString(wrapper);
System.out.println("json = " + json);

BulkDTOWrapper wrapper2 = om.readValue(json, BulkDTOWrapper.class);
System.out.println(wrapper2.getBulks().size());
}
}


Related Topics



Leave a reply



Submit