Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 1019 Vote(s) - 3.48 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Validation of a list of objects in Spring

#11
Here's my attempt to reconcile the many different answers.

[Lebecca's answer][1] works without the need for a wrapper, as [Paul's answer][2] requires, because `@Validated` placed on the class enables the [method validation feature][3] of the Bean Validation API.

The [Hibernate Validator documentation][4] specifically explains:

> [...] the @Valid annotation can be used to mark executable parameters and return values for cascaded validation.

> [...]

> Cascaded validation can not only be applied to simple object
> references but also to collection-typed parameters and return values.
> This means when putting the @Valid annotation to a parameter or return
> value which

> * is an array

> * implements java.lang.Iterable

> * or implements java.util.Map

>each contained element gets validated.

If you need to validate a collection of _Beans_, this is the most convenient way (make sure to also implement an `@ExceptionHandler` as required).

If you need to validate a collection of _Non-Beans_, e.g. a `List<String>` where each element must match a pattern, you can use [container element constraints][5] like this:

controllerMethod(List<@Pattern(regexp="pattern") String> strings)

There's also the possibility to *only* use `@Valid` on a controller method parameter (which must then be a Bean type) *without* also placing `@Validated` on the class. In that case, you get an appropriate, detailed HTTP 400 response "for free", i.e. without the need for a custom `@ExceptionHandler`. But this doesn't apply the cascading validation, so you cannot validate something like `@Valid List<SomeBean> beans`, nor does it support container element constraints.

And finally, you can combine the latter approach with an extra parameter added to the method of type `BindingResult`. This won't trigger an automatic error response in the case of a validation error, but instead you must inspect the injected `BindingResult` yourself in the method body and act accordingly (which allows for more flexibility). That is described in [this][6] comprehensive answer.


[1]:

[To see links please register here]

[2]:

[To see links please register here]

[3]:

[To see links please register here]

[4]:

[To see links please register here]

[5]:

[To see links please register here]

[6]:

[To see links please register here]

Reply

#12
# 1 TL;DR

I tried to use Paul's method in my project, but some people said it's too complex. Not long after that, I find another easy way which works like code below:

```java
@Validated
@RestController
@RequestMapping("/parent")
public class ParentController {

private FatherRepository fatherRepository;

/**
* DI
*/
public ParentController(FatherRepository fatherRepository) {
this.fatherRepository = fatherRepository;
}

@PostMapping("/test")
public void test(@RequestBody @Valid List<Father> fathers) {

}
}
```

It works and easy to use. The key point is the @Valiated annotation on the class. Btw, it's springBootVersion = '2.0.4.RELEASE' that I use.

# 2 Exception handling

As discussed in comments, exceptions can be handled like code below:

```java
@RestControllerAdvice
@Component
public class ControllerExceptionHandler {

/**
* handle controller methods parameter validation exceptions
*
* @param exception ex
* @return wrapped result
*/
@ExceptionHandler
@ResponseBody
@ResponseStatus(HttpStatus.OK)
public DataContainer handle(ConstraintViolationException exception) {

Set<ConstraintViolation<?>> violations = exception.getConstraintViolations();
StringBuilder builder = new StringBuilder();
for (ConstraintViolation<?> violation : violations) {
builder.append(violation.getMessage());
break;
}
DataContainer container = new DataContainer(CommonCode.PARAMETER_ERROR_CODE, builder.toString());
return container;
}
}
```

Taking http status code as representing network is ok and only first violation message is returned here. You may change it to satisfy customized requirements.

# 3 How it works (code part)

With @Validated on class level, parameters of methods are validated by what called method-level validation in spring boot, which is not only worked for controllers, but any bean the `IOC` container managed.

- [Spring boot docs about method level validation](

[To see links please register here]

)
- [A more detailed blog with test about method level validation](

[To see links please register here]

)

By the way, the methods in method level validation (short as validation A) is enhanced by

- org.springframework.validation.beanvalidation.MethodValidationInterceptor

while the typical spring boot controller methods validation (short as validation B) is processed in

- org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor

Both of them lead the actual validation operation to `org.hibernate.validator.internal.engine.ValidatorImpl` by default, but the methods they call are different, which leads to the differences in validation logic.

- `MethodValidationInterceptor` call `validateParameters` method in `ValidatorImpl`
- `RequestResponseBodyMethodProcessor` call `validate` method in `ValidatorImpl`

They are different methods with different functions, so lead to different results in validation A/B, the typical point is the validation of list object:

- A triggers constraint check on element of collection object while B not

# 4 How it works (specification part)


The [JSR-303](

[To see links please register here]

) defines functions of the methods we discussed above.

`validate` method is explained in the [validation method](

[To see links please register here]

) part, and the implementation must obey the logic defined in [validation routine](

[To see links please register here]

), in which it states that it will execute all the constraint validation for all reachable fields of the object, this is why element of `List` object (or other collection instance) cannot be validated via this method - the elements of the collection are not fields of the collection instance.

But `validateParameters`, JSR-303 actually doesn't treat it as main topic and put it in `Appendix C. Proposal for method-level validation`. It provides some description:

```text
The constraints declarations evaluated are the constraints hosted on the parameters of the method or constructor. If @Valid is placed on a parameter, constraints declared on the object itself are considered.

validateReturnedValue evaluates the constraints hosted on the method itself. If @Valid is placed on the method, the constraints declared on the object itself are considered.

public @NotNull String saveItem(@Valid @NotNull Item item, @Max(23) BigDecimal price)

In the previous example,

- item is validated against @NotNull and all the constraints it hosts
- price is validated against @Max(23)
- the result of saveItem is validated against @NotNull
```

and exclaim that `Bean Validation providers are free to implement this proposal as a specific extension`. As far as I know, the `Hibernate Validation` project implements this method, makes constraints works on the object itself, and element of collection object.


## 5 Some complain

I don't know why the spring framework guys call `validate` in `RequestResponseBodyMethodProcessor`, makes lots of related questions appeare in stackoverflow. Maybe it's just because http post body data usually is a form data, and can be represented by a java bean naturally. If it's me, I'll call the `validateParametes` in `RequestResponseBodyMethodProcessor` for easy use.
Reply

#13
(this answer is in **Kotlin**, for **Java** see

[To see links please register here]

)

For those using **kotlin** and **jackson**, here is the `ValidatedList` class that **do not require wrapping**, that is, it will still be serialized/deserialized as a usual list:

```kotlin
class ValidatedList<E> {
/**
* By default, spring-boot cannot validate lists, as they are generic AND do not conform to the Java Bean definition.
* This is one work-around: create a wrapper that fits the Java Bean definition, and use Jackson annotations to
* make the wrapper disappear upon (de)serialization.
* Do not change anything (such as making the _value field private) or it won't work anymore !
*
* Usage:
* ```
* @PostMapping("/something")
* fun someRestControllerMethod(@Valid @RequestBody pojoList: ValidatedList<SomePOJOClass>){
* // access list with:
* pojoList.values
*}
* ```
*/

@JsonValue
@Valid
@NotNull
@Size(min = 1, message = "array body must contain at least one item.")
var _values: List<E>? = null

val values: List<E>
get() = _values!!

@JsonCreator
constructor(vararg list: E) {
this._values = list.asList()
}
}
```

Advantages:
* no need for the `@Validated` annotation
* will throw an error if the body is an empty array (see `@Size`)
* the exception will be mapped correctly to `400 Bad Request` (which is not the case when using `javax` and `@Validated` annotation)


----------


Example:

```kotlin
data class N(
@field:Min(value = 0, message = "id must be positive.")
val id: Long? = null,

@field:NotNull
@field:Size(min = 2, max = 32, message = "wrong size: should be 32 chars long.")
val token: String? = null
)
```

```kotlin
@RestController
class XController {
@PostMapping("/ns")
fun getNs(@Valid @NotNull @RequestBody wrap: ListWrapper<N>) = wrap
}
```

Submit ok:
```bash
curl -H "Content-Type: application/json" -X POST

[To see links please register here]

-d '[{"id": 11, "token": "something"}]'
```
```json
[{"id" : 11, "token" : "something"}]
```

Submit empty body:
```bash
curl -H "Content-Type: application/json" -X POST

[To see links please register here]

-d '[]'
```
```json
{
"timestamp" : "2020-09-25T08:49:30.324+00:00",
"message" : "Validation failed for object='listWrapper'. Error count: 1",
"error" : "Bad Request",
"path" : "/ns",
"status" : 400,
"exception" : "org.springframework.web.bind.MethodArgumentNotValidException",
"trace":"org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.example.demo.test.XController$ListWrapper<com.example.demo.test.XController$N> com.example.demo.test.XController.getNs(com.example.demo.test.XController$ListWrapper<com.example.demo.test.XController$N>): [Field error in object 'listWrapper' on field '_values': rejected value [[]]; codes [Size.listWrapper._values,Size._values,Size.java.util.List,Size]; [...]"
}
```

Submit invalid items:

```bash
curl -H "Content-Type: application/json" -X POST

[To see links please register here]

-d '[{"id": -11, "token": ""}]'
```
```json
{
"message" : "Validation failed for object='listWrapper'. Error count: 2",
"path" : "/ns",
"exception" : "org.springframework.web.bind.MethodArgumentNotValidException",
"timestamp" : "2020-09-25T08:49:54.505+00:00",
"error" : "Bad Request",
"status" : 400,
"trace":"org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.example.demo.test.XController$ListWrapper<com.example.demo.test.XController$N> com.example.demo.test.XController.getNs(com.example.demo.test.XController$ListWrapper<com.example.demo.test.XController$N>) with 2 errors: [...]"
}
```
Reply

#14
With the Spring Boot 2.2.2 version...

Here's the piece of code:-

import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Validated
public class MyController {

@PostMapping(value = "/test", consumes = "application/json", produces = "application/json")
public String test(@Valid @RequestBody List<Student> st) {
System.out.println("-------------test Method-------");
return "Its' Success";
}
}

class Student{

@NotBlank
String name;
@NotBlank
String password;
@NotBlank
String email;

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}

**List of JSON Data:-**

Notice **name** is blank in the second Student object.

[
{
"name": "Sreepad",
"password": "sddwh",
"email": "[email protected]"
},
{
"name": "",
"password": "sddwh",
"email": "[email protected]"
}
]

**Error Description:-**

javax.validation.ConstraintViolationException: test.st[1].name: must not be blank.

**Note:** List<T> and String won't be validated at method parameter level if you remove **@Validated** at Class level.

[SpringBoot doc says][1]:-

**17. Validation**

The method validation feature supported by Bean Validation 1.1 is automatically enabled as long as a JSR-303 implementation (such as Hibernate validator) is on the classpath. This lets bean methods be annotated with javax.validation constraints on their parameters and/or on their return value. **Target classes with such annotated methods need to be annotated with the @Validated annotation at the type level for their methods to be searched for inline constraint annotations.**


[1]:

[To see links please register here]

Reply

#15
Using *Spring Boot* `2.4.1`:

1. Add the `@Validated` annotation to the class

1. Move the `@Valid` annotation inside the diamond operator:

```java
@RestController
@Validated // <-- This activates the Spring Validation AOP interceptor
public class MyController {

...
@RequestBody List<@Valid CompanyTag> categories
// ^^^ - NOTE: the @Valid annotation is inside <> brackets
```
Reply

#16
For those using spring boot (I was using 2.6.7), what worked for me was adding the spring-boot-starter-validation dependency:

```
org.springframework.boot:spring-boot-starter-validation
```
Reply

#17
I did the below steps to make validation work on lists:

1. Annotate the rest controller with `@Validated` at the class level
2. Add @Valid before the generic type in the list, i.e `List<@Valid MyClass>`

Also, found that if the validation failed I got javax.validation.ConstraintViolationException
Reply

#18
I am using

- Kotlin 1.6
- Spring Boot 2.6.6
- Spring Webflux

I needed to validate a `List<String>` request parameters. Here is my working example (inspired by some of previous answers)

@RestController
@Validated
class SearchController {
@GetMapping("/search")
fun search(
@Valid
@RequestParam(value = "term") terms: List<Term>,
): Mono<ResponseEntity<SearchResponse>> {...}
}

data class Term(
@field:NotEmpty(
message = "Term is required"
)
@field:Size(
min = 2,
max = 500,
message = "Term length out of range"
)
val term: String
)
in build.gradle.kts

dependencies {
implementation("org.springframework.boot:spring-boot-starter-validation")
}



Reply

#19
I have done custom Validation for the list of parameters we're passing... `

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = PatternListValidator.class)
public @interface PatternList {

public String regexp();
public String message() default "Invalid inputs";
public Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}`

Created the above custom validation annotation / interface and implemented the same with the business logic


import java.util.List;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class PatternListValidator implements ConstraintValidator<PatternList, List<String>> {

private String regexp;

@Override
public void initialize(PatternList patternList) {
this.regexp = patternList.regexp();
}

@Override
public boolean isValid(List<String> dataList, ConstraintValidatorContext context) {

for(String data : dataList) {
if(!data.matches(regexp)) {
return false;
}
}
return true;
}

}

used this @PatternList annotation in my controller class as api method parameter as below



Public ResponseEntity<Object> getStudents(
@ApiParam(name = "studentIds", value = "Fetch students for athlete and art. Example values: 1234, 5432", required = true) @PatternList(regexp = "\\d+", message = "student Id's can contain only numbers") @RequestParam(value = "studentId", required = true) List<String> studentIds) {

business logic goes here....

}



Reply

#20
With the later versions of spring, you can now do this.

@RequestMapping(value="/map/update", method=RequestMethod.POST, produces = "application/json; charset=utf-8")
@ResponseBody
public ResponseEntityWrapper updateMapTheme(
HttpServletRequest request,
@RequestBody List<@Valid CompanyTag> categories,
HttpServletResponse response
) throws ResourceNotFoundException, AuthorizationException {
...
}

the @Valid annotation is in the generic param.

If you are using a custom javax validation annotation, make sure to add TYPE_USE to the annotation targe

@Target({ ElementType.TYPE_USE})
public @interface ValidationAnnotation {.. }

Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through