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
Discord Jda - Invalid Member List
Is There Any Performance Reason to Declare Method Parameters Final in Java
Itext Merge Documents with Acrofields
Using Setvalueat to Recreate Mutually Exclusive Check Boxes
Appearance of Java Security Dialog
Finding Largest Prime Number Out of 600851475143
Java 8 Lambdas Group List into Map
Repaint Swing Button with Different Gradient
How to Make Jscrollpane (In Borderlayout, Containing JPAnel) Smoothly Autoscroll
Passing Directly an Array Initializer to a Method Parameter Doesn't Work
How to Enumerate Ip Addresses of All Enabled Nic Cards from Java
Apache PDFbox: Problems with Encoding
Java - Generate Random Range of Specific Numbers Without Duplication of Those Numbers - How To
How to Read Properties File in Web Application