Spring supports two different validation methods: Spring validation and JSR-303 bean validation. Both can be used by defining a Spring validator that delegates to other delegators including the bean validator. So far so good.
But when annotating methods to actually request validation, it's another story. I can annotate like this
#RequestMapping(value = "/object", method = RequestMethod.POST)
public #ResponseBody TestObject create(#Valid #RequestBody TestObject obj, BindingResult result) {
or like this
#RequestMapping(value = "/object", method = RequestMethod.POST)
public #ResponseBody TestObject create(#Validated #RequestBody TestObject obj, BindingResult result) {
Here, #Valid is javax.validation.Valid, and #Validated is org.springframework.validation.annotation.Validated. The docs for the latter say
Variant of JSR-303's Valid, supporting the specification of validation
groups. Designed for convenient use with Spring's JSR-303 support but
not JSR-303 specific.
which doesn't help much because it doesn't tell exactly how it's different. If at all. Both seem to be working pretty fine for me.
A more straight forward answer.
For those who still don't know what on earth is "validation group".
Usage for #Valid Validation
Controller:
#RequestMapping(value = "createAccount")
public String stepOne(#Valid Account account) {...}
Form object:
public class Account {
#NotBlank
private String username;
#Email
#NotBlank
private String email;
}
Usage for #Validated Validation Group
Source: http://blog.codeleak.pl/2014/08/validation-groups-in-spring-mvc.html
Controller:
#RequestMapping(value = "stepOne")
public String stepOne(#Validated(Account.ValidationStepOne.class) Account account) {...}
#RequestMapping(value = "stepTwo")
public String stepTwo(#Validated(Account.ValidationStepTwo.class) Account account) {...}
Form object:
public class Account {
#NotBlank(groups = {ValidationStepOne.class})
private String username;
#Email(groups = {ValidationStepOne.class})
#NotBlank(groups = {ValidationStepOne.class})
private String email;
#NotBlank(groups = {ValidationStepTwo.class})
#StrongPassword(groups = {ValidationStepTwo.class})
private String password;
#NotBlank(groups = {ValidationStepTwo.class})
private String confirmedPassword;
}
As you quoted from the documentation, #Validated was added to support "validation groups", i.e. group of fields in the validated bean. This can be used in multi step forms where you may validate name, email, etc.. in first step and then other fields in following step(s).
The reason why this wasn't added into #Valid annotation is because that it is standardized using the java community process (JSR-303), which takes time and Spring developers wanted to allow people to use this functionality sooner.
Go to this jira ticket to see how the annotation came into existence.
In the example code snippets of the question, #Valid and #Validated make no difference. But if the #RequestBody is annotated with a List object, or is a string value annotated by #RequestParam, the validation will not take effect.
We can use the #Validated's method-level validation capability to make it work. To achieve this, the key point is to place #Validated on the class. This may be another important difference between #Valid and #Validated in spring framework.
Refrence
Spring boot docs
Just for simplifying:
#Validated annotation is a class-level annotation that we can use to tell Spring to validate parameters that are passed into a method of the annotated class.
and
#Valid annotation on method parameters and fields to tell Spring that we want a method parameter or field to be validated.
besides above, you can only apply #Valid on a domain/field for nested validation, not with a #validated.
#Validated can be used for a class:
#Validated
public class Person {
#Size(min=3)
private String name;
...
Related
I have the following dependency in the pom.xml for spring boot validation and controller method argument updated with #Valid annotation but still no validation errors when I submit a request with null or not empty values. Any Idea like why validation not getting trigged even though hibernate validation library on the classpath.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
#RequestMapping("hello")
public ResponseEntity<String> message(#Valid #RequestBody MyRequest request)
{
public class MyRequest {
#NotNull(message = "Client ID is mandatory")
#NotEmpty
private String clientId;
#NotEmpty private String employId;
anotations #NotEmpty and #NotNull and #Valid must be imported from javax.validation .... They are all in the jakarta.Validation api specifications otherwise it won't work
Everything seems fine just add #Validated annotation at Controller class level. I hope this works.
In Spring data JPA there are annotations that can be used to set up validations for entities in a declarative manner. They can be found in javax.validation.constraints.* or additionally in org.hibernate.validator.constraints.* (in the case when Hibernate is plugged in).
Example:
#NotNull
private String lastName;
However, if the case of Spring data r2dbc they do not work out of the box.
Is there any simple and smooth way to set up validations for entities in Spring data r2dbc? That should not be too difficult in my opinion because probably it does not require full ORM support, just a matter of callbacks for checking object fields before persisting it.
In the RestController, validate the incoming request body using the annotations from Bean validation and Hibernate validators.
For RouterFunction or manal validation in your service, inject a Validator or ValidatorFactory to validate the request body before the data is (converted and) persisted into databases.
JPA is tightly integrated with Bean Validation/Hibernate Validator, besides validation, it will affect the generated schemes by default.
In a real world application, do not use the data layered entity classes in the web layer as request body class.
It works very fine with Bean Validation and Hibernate Validation.
You can use all the same annotations you did in JPA, since you do it in your DTOs.
See the example:
#Data
public class UserDto {
private String name;
#NotNull(message = "Last name can not be empty")
private String lastName;
#Min(value = 10, message = "Required min age is 10")
#Max(value = 50, message = "Required max age is 50")
private int age;
}
Then your controller would be annotated:
#RestController
#RequestMapping("user")
public class RegistrationController {
#Autowired
private UserService userService;
#PostMapping("register")
public Mono<UserDto> register(#Valid #RequestBody Mono<UserDto> userDtoMono{
return this.userService.registerUser(userDtoMono);
}
}
The only thing now is that you have to personalize your message, so that it returns the one you have set in your DTO. To override it you should create an ExceptionHandler.
#ControllerAdvice
public class ValidationHandler {
#ExceptionHandler(WebExchangeBindException.class)
public ResponseEntity<List<String>> handleException(WebExchangeBindException e) {
var errors = e.getBindingResult()
.getAllErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
return ResponseEntity.badRequest().body(errors);
}
}
I've been using it in my projects and got this example from: https://www.vinsguru.com/spring-webflux-validation/
My question is similar to Resteasy Bean Validation Not Being Invoked. The solutions there don't work, though.
I'm using Resteasy 3.0.9.Final with resteasy-validator-provider-11 in my pom. I'm launching the whole thing using a custom Jetty class.
Weirdly, validation is working fine on #PathParams, but not on beans.
#POST
#Path("/foo/{myParam}")
public Message post(MyBean myBean, #PathParam("myParam") #Size(min=5) String myParam) {
return new Message("bean:" + myBean.toString());
}
public static class MyBean {
#NotNull
public String myStr;
#Max(value = 3)
public int myInt;
public String toString() {
return myStr + myInt;
}
}
In this case, the #Size constraint on myParam is working fine. But the #NotNull and #Max constraints in MyBean are not getting invoked.
Am I missing an annotation somewhere?
Here's one more clue. My logs include these entries:
2014-12-30 12:16:56 org.hibernate.validator.internal.util.Version 6446 INFO HV000001: Hibernate Validator 5.0.1.Final
2014-12-30 12:16:56 org.jboss.resteasy.plugins.validation.AbstractValidatorContextResolver 6477 INFO Unable to find CDI supporting ValidatorFactory. Using default ValidatorFactory
I believe, but not 100% sure, that the issue is that you're missing #Valid on the MyBean parameter. I would also recommend to make it a separate class, rather than a static class.
Per the spec, validation constraints on methods where the object is a complex object need to have the parameter annotated #Valid to ensure that the constraints are cascaded.
JSR 303 #Valid annotation can be used to validate input objects to a controller method, as demonstrated on Mkyong.com
Is it possible to use #Valid annotation to validate primitive types like int and long[]? If so, how?
Here is an example Spring MVC method signature that needs to be validated that the parameters are above 0:
#RequestMapping(value = { "/delete" }, method = RequestMethod.POST)
ModelAndView deleteBulk(#RequestParam("userId") int userId, #RequestParam("ids") long[] ids) {
No, it can't be done. It's for bean validation, as described here.
you can validate #RequestParam/#PathVariable simple objects and primitives, if put #Validated on full controller.
And if you don't use spring-boot, you should declare MethodValidationPostProcessor by yourself
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
i had lots of problems adding Secured annotations to my Controllers.
it turns out letting my Controller implement an InitializingBean was a bad idea.
public class MyController implements InitializingBean {
#Secured(value="ROLE_ADMIN")
#RequestMapping(method = RequestMethod.GET, value = "/{id}/edit")
public String getView(Model model, #PathVariable("id") long id) {
return "some view";
}
}
this failed with:
WARN PageNotFound:962 - No mapping
found for HTTP request with URI[...]
removing the #Secured Annotation would work, but obviously i didn't want to do that.
after lots of wasted time on the net i noticed the last difference beetween a working and a non working controller was that it implemented the InitializingBean Interface. And now this works like a charm:
public class MyController{
#Secured(value="ROLE_ADMIN")
#RequestMapping(method = RequestMethod.GET, value = "/{id}/edit")
public String getView(Model model, #PathVariable("id") long id) {
return "some view";
}
}
Can anyone help me understand that behaviour?
This happens because access to the annotations is lost when security aspect is applied using JDK dynamic proxy, which happens by default when advised bean implements any interfaces.
To solve this problem, you should tell Spring Security to apply target-class-based proxies only, using <global-method-security proxy-target-class = "true" ...> ... (<aop:config proxy-target-class = "true" /> works too).
More about AOP proxies here.