springboot #validated and bindingresult - java

#Controller
#RequestMapping("/")
#Validated
public class AddressWebController {
#GetMapping(value = {"/{id}")
public String editForm(#PathVariable(value = "id") #Positive Long id, Model model) {
// process
}
#PutMapping(value = {"/edit"})
public String editProcess(#ModelAttribute #Valid Form form,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// ???
}
// process
}
}
public class Form {
#NotBlank
private String name;
}
curl -XGET 'http://localhost/5'
is pass
curl -XGET 'http://localhost/-5'
A ConstraintViolationException exception is thrown. Due to #Validated and #Positive
curl 'http://localhost/edit' --data-raw '_method=PUT&name='
I expected it to come in as bindingResult.hasErrors() in that part, but the same ConstraintViolationException is thrown.
When #Validated is removed, bindingResult.hasErrors() works, but #Positive annotation does not.
How can I use #Validated annotation and BindingResult together?

Related

Spring boot REST controller: different custom validators for POST and PUT methods receiving the same object

I have a Spring Boot Controller with POST and PUT method's and a custom validator.
#Autowired
PersonValidator validator;
#InitBinder
protected void initBinder(final WebDataBinder binder) {
binder.addValidators(validator);
}
#PostMapping
public ResponseEntity<String> save(#Valid #RequestBody Person person) {}
#PutMapping
public ResponseEntity<String> update(#Valid #RequestBody Person person) {}
Currently both POST and PUT methods are using the same validation rules. QUESTION: I would need to have different validation rules for PUT and POST. Any ideas on how to proceed, how can I use different custom validators within the same RestController?
You can integrate Hibernate Validator in your application if you are using Spring boot. It seems that you need to implement Grouping constraints (https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#chapter-groups)
e.g.
Entity
#NotEmpty(groups = PutValidation.class)
private String field1;
#Size(min = 3, groups = PutValidation.class)
#Size(min = 5, groups = PostValidation.class)
private String field2;
#Min(value = 18, groups = {PostValidation.class,PutValidation.class,Default.class})
private int age;
//Getters/setters
Groups (these are nothing but empty interfaces)
public interface PostValidation {
}
public interface PutValidation {
}
How to use group of constraints in Controller
#PostMapping
public ResponseEntity<String> save(#Validated(PostValidation.class) #RequestBody Person person) {}
#PutMapping
public ResponseEntity<String> update(#Validated(PutValidation.class) #RequestBody Person person) {}
I hope this can help to solve your problem.
Cheers!
You could create your own annotations for each Validator and replace #Valid at each endpoint.
Have a look at #InRange: https://lmonkiewicz.com/programming/get-noticed-2017/spring-boot-rest-request-validation/

How to use #valid annotation in complex model

I have complex model request class and I am using valid annotation. But it doesnt work in subclasses.
cause=java.lang.NullPointerException detailMessage=HV000028:
Unexpected exception during isValid call.
public class ChangeBlackListStatusRequest {
#Valid
List<CategoryChangeRequest> categoryChangeRequestList;
}
public class CategoryChangeRequest {
#Valid
List<Category> categoryList;
#Valid
List<Service> serviceList;
#Valid
List<Merchant> merchantList;
#Valid
List<Aggregator> aggregatorList;
}

Binding data to RequestBody (415 error)

I have the following in my Controller:
#Controller
public class GreetingController {
#PostMapping("/register")
public String registerUser(#RequestBody UserEntity request) throws ServletException, IOException {
System.out.println(request.getId());
return "register";
}
}
The UserEntity is:
#Entity
#Table(name = "users")
public class UserEntity {
private int id;
private String name;
private String email;
private String password;
I get the following error:
There was an unexpected error (type=Unsupported Media Type, status=415).
Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported
Note that I have Jackson installed (from this question: Jackson Databind classpath issue).
Additionally, I am able to use the public String registerUser(HttpServletRequest request) fine, but when I try using #RequestBody it just gives me that error.
How would I get the #RequestBody to be the UserEntity?
You are using the header value "application/x-www-form-urlencoded;charset=UTF-8" in the request while you should use "application/json"

Spring #RestController with #RequestBody #Valid by default

Usually we write
#RestController
public class TestController {
#RequestMapping(value = "/test")
public String test2(#RequestBody #Valid TestClass req) {
return "test2";
}
}
But since it is a REST controller is it possible to configure Spring to use #RequestBody #Valid by default, so these annotations could be omitted?

Spring boot, how to use #Valid with List<T>

I am trying to put validation to a Spring Boot project. So I put #NotNull annotation to Entity fields. In controller I check it like this:
#RequestMapping(value="", method = RequestMethod.POST)
public DataResponse add(#RequestBody #Valid Status status, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return new DataResponse(false, bindingResult.toString());
}
statusService.add(status);
return new DataResponse(true, "");
}
This works. But when I make it with input List<Status> statuses, it doesn't work.
#RequestMapping(value="/bulk", method = RequestMethod.POST)
public List<DataResponse> bulkAdd(#RequestBody #Valid List<Status> statuses, BindingResult bindingResult) {
// some code here
}
Basically, what I want is to apply validation check like in the add method to each Status object in the requestbody list. So, the sender will now which objects have fault and which has not.
How can I do this in a simple, fast way?
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.
Just mark controller with #Validated annotation.
It will throw ConstraintViolationException, so probably you will want to map it to 400: BAD_REQUEST:
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice(annotations = Validated.class)
public class ValidatedExceptionHandler {
#ExceptionHandler
public ResponseEntity<Object> handle(ConstraintViolationException exception) {
List<String> errors = exception.getConstraintViolations()
.stream()
.map(this::toString)
.collect(Collectors.toList());
return new ResponseEntity<>(new ErrorResponseBody(exception.getLocalizedMessage(), errors),
HttpStatus.BAD_REQUEST);
}
private String toString(ConstraintViolation<?> violation) {
return Formatter.format("{} {}: {}",
violation.getRootBeanClass().getName(),
violation.getPropertyPath(),
violation.getMessage());
}
public static class ErrorResponseBody {
private String message;
private List<String> errors;
}
}
#RestController
#Validated
#RequestMapping("/products")
public class ProductController {
#PostMapping
#Validated(MyGroup.class)
public ResponseEntity<List<Product>> createProducts(
#RequestBody List<#Valid Product> products
) throws Exception {
....
}
}
with using Kotlin and Spring Boot Validator
#RestController
#Validated
class ProductController {
#PostMapping("/bulk")
fun bulkAdd(
#Valid
#RequestBody statuses: List<Status>,
): ResponseEntity<DataResponse>> {...}
}
data class Status(
#field:NotNull
val status: String
)

Categories