In RestController, I am using #Valid annotation to validate custom class's members as below
#PostMapping("/path1")
public ResponseEntity<String> function1(#Valid CustomClass customClass) {
...
}
I defined custom validator to CustomClass's specific member, for instance, #CustomValid.
And, I have to use #Validated for other function's #RequestParam
#PostMapping("/path2")
public ResponseEntity<String> function2(#RequestParam("param") #NotBlank String param) {
...
}
To run second validator, I have to add #Validated annotation at class level.
But when I add #Validated, the #CustomValid called twice. When I remove #Validated, #NotBlank does not called.
How can I fix this?
Related
I have the following classes used for validating a password.
public class PasswordConstraintValidator implements ConstraintValidator<ValidPassword, String> {
#Override
public void initialize(ValidPassword constraintAnnotation) {
}
#Override
public boolean isValid(String password, ConstraintValidatorContext context) {
PasswordValidator validator = new PasswordValidator(Arrays.asList(
// at least 8 characters
new LengthRule(8, 30),
// at least one upper-case character
new CharacterRule(EnglishCharacterData.UpperCase, 1),
// at least one lower-case character
new CharacterRule(EnglishCharacterData.LowerCase, 1),
// at least one digit character
new CharacterRule(EnglishCharacterData.Digit, 1),
// at least one symbol (special character)
new CharacterRule(EnglishCharacterData.Special, 1),
// no whitespace
new WhitespaceRule()
));
RuleResult result = validator.validate(new PasswordData(password));
if (result.isValid()) {
return true;
}
List<String> messages = validator.getMessages(result);
String messageTemplate = messages.stream().collect(Collectors.joining(","));
context.buildConstraintViolationWithTemplate(messageTemplate)
.addConstraintViolation()
.disableDefaultConstraintViolation();
return false;
}
}
#Documented
#Constraint(validatedBy = PasswordConstraintValidator.class)
#Target( {ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.LOCAL_VARIABLE, ElementType.TYPE_PARAMETER, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidPassword {
String message() default "Invalid Password";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Adding the #ValidPassword annotation for a field in a class works.
However when I try adding the annotation to a parameter in a function, the validator is never called/reached.
public void resetUserPassword(Integer userId, #ValidPassword String newPassword) {
}
Also adding the annotation here doesn't work either:
#PostMapping("/user/resetPassword/{id}")
public ResponseEntity<?> resetUserPassword(#PathVariable("userId") Integer userId, #Valid #ValidPassword #RequestBody String newPassword) {
userService.resetUserPassword(userId, newPassword)
return ResponseEntity.ok().build();
}
I don't think I am missing any dependencies, so I'm not sure where the problem is.
The annotation #Validated defined at class-level annotation is
necessary to trigger method validation for a specific bean to begin
with.
Also in other words
The #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.
Refer this link https://github.com/spring-projects/spring-framework/issues/11039 to find out the origin for #Validated
Usage:
As you have the below method with your custom annotation #ValidPassword with your #Valid
#PostMapping("/user/resetPassword/{id}")
public ResponseEntity<?> resetUserPassword(#PathVariable("userId") Integer userId, #Valid #ValidPassword #RequestBody String newPassword) {
userService.resetUserPassword(userId, newPassword)
return ResponseEntity.ok().build();
}
#Valid
It is used for enabling the whole object validation As you can see in the below example the #NotNull #Size and #NotBlank will be invoked to validate the user input or supplied values present in the object.
Eg:
public class DummyUser{
#NotNull
#Size(min =8)
private String password;
#NotBlank
private String username;
}
#Validated
But, As per your case you want your custom validation to be invoked on the method parameter , Hence you need to provide the hint to spring to invoke the custom validation. So, to do that you have declare the #Validated annotation at class level in your controller.
So these are the reasons for your validation to start working after you have annotated your controller class with #Validated annotation.
You need to add #Validated annotation on the controller class or another class, where you want to validate method parameter with your custom validation.
There was an explanation in spring-boot 2.1.x documentation about this kind of method-level validation, but I couldn't find it in current 2.7.x docs.
In general it's a spring-framework feature, that can be found here. In a non-boot project you'll need to create a bean of type MethodValidationPostProcessor manually, but spring-boot auto-configurates this bean for you - the autoconfiguration can be found in ValidationAutoConfiguration class.
According to java-docs of MethodValidationPostProcessor, target classes with JSR-303 constraint annotated methods need to be annotated with Spring's #Validated annotation at the type level, for their methods to be searched for inline constraint annotations. Validation groups can be specified through #Validated as well. By default, JSR-303 will validate against its default group only.
I'm trying to remove repeating code from my Spring Controllers, specifically - removing the need to execute the validator.validate(form, bindingResult) from the start of many of my functions.
I have a few classes that have corresponding validator classes that implement Spring's validator interface. I have searched around to try and find an answer but I'm having trouble finding one that really matches this.
Snippet of Person Form Class with annotated attributes
public class Person {
#Size(min=1, message="Name missing")
private String name;
#Size(min=1, message="Age missing")
private String age;
.... getters and setters etc.
Person Validator Class
#Component
public class PersonValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Person.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
errors.reject("No sir!");
}
}
Ideally, I'd like to be able to have all the errors contained within the BindingResult, including the errors from the validator class. So that when I use the #Validated annotation my BindingResult is fully populated with all the errors from both the simple annotations and the custom validator.
Desired Outcome
#RequestMapping(value="/save", method=RequestMethod.POST)
public #ResponseBody String save(#Validated #RequestBody Person personForm, BindingResult bindingResult, HttpServletRequest request)
{
bindingResult.getAllErrors(); <-- fully pop with annotation and custom validator errors
Instead of:
#RequestMapping(value="/save", method=RequestMethod.POST)
public #ResponseBody String save(#Validated #RequestBody Person personForm, BindingResult bindingResult, HttpServletRequest request)
{
personValidator.validate(person, bindingResult) <-- Populate bindingResult with customer validator errors, if any
bindingResult.getAllErrors();
Has anyone got any neat examples they can share to get around this?
Thanks!
You need to add the validator to the databinder for multiple validators to work. In your code add an #InitBinder method and add the PersonValidator to the WebDataBinder.
#InitBinder("personForm")
public void initBinder(WebDataBinder wdb) {
wdb.addValidators(personValidator);
}
Will bind a validator to the personForm model object.
This will configure a global rule that this validator is applied to all bindings/conversions. If you want to limit this to a certain model you can specify the name of the model in the #InitBinder.
#InitBinder
public void initBinder(WebDataBinder wdb) {
wdb.addValidators(personValidator);
}
As possible solution, you can define your own custom annotation and CustomConstraintValidator that will implement interface ConstraintValidator<A extends Annotation, T>.
At the end BindingResult will contain either default validator and your custom validator errors.
Here is a good example. If I understand your question correctly of course.
I have a Java class (MyResponse) that is returned by a multiple RestController methods and has a lot of fields.
#RequestMapping(value = "offering", method=RequestMethod.POST)
public ResponseEntity<MyResponse> postOffering(...) {}
#RequestMapping(value = "someOtherMethod", method=RequestMethod.POST)
public ResponseEntity<MyResponse> someOtherMethod(...) {}
I want to ignore (e.g. not serialize it) one of the properties for just one method.
I don't want to ignore null fields for the class, because it may have a side effect on other fields.
#JsonInclude(Include.NON_NULL)
public class MyResponse { ... }
The JsonView looks good, but as far as I understand I have to annotate all other fields in the class with a #JsonView except the one that I want to ignore which sounds clumsy. If there is a way to do something like "reverse JsonView" it will be great.
Any ideas on how to ignore a property for a controller method?
Props to this guy.
By default (and in Spring Boot) MapperFeature.DEFAULT_VIEW_INCLUSION is enabled in Jackson. That means that all fields are included by default.
But if you annotate any field with a view that is different than the one on the controller method this field will be ignored.
public class View {
public interface Default{}
public interface Ignore{}
}
#JsonView(View.Default.class) //this method will ignore fields that are not annotated with View.Default
#RequestMapping(value = "offering", method=RequestMethod.POST)
public ResponseEntity<MyResponse> postOffering(...) {}
//this method will serialize all fields
#RequestMapping(value = "someOtherMethod", method=RequestMethod.POST)
public ResponseEntity<MyResponse> someOtherMethod(...) {}
public class MyResponse {
#JsonView(View.Ignore.class)
private String filed1;
private String field2;
}
I have a field in my Entity with #JsonView annotation:
#JsonView(View.Secure.class)
private String password;
Inside my controller:
#RequestMapping(method = RequestMethod.GET, produces = "application/json")
#JsonView(View.Secure.class)
public ResponseEntity<?> getAllUsers(){
return createUserListResponse();
}
My View class:
public class View {
public static class Secure {}
}
I've expected that response will contain only "password" field, but instead it contains nothing. When i remove annotation #JsonView(View.Secure.class) from Controller - it works as usual and returns all fields. What am i doing wrong? Is it required to add some additional configuration into Spring config?
I used this tutorial: https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring
I was looking at other questions about Spring custom validators but unfortunately I could not solve my problem with the proposed answers.
My problem is the following: I have an entity (Account) and I created a custom validator (AccountValidator) which I use in a controller (RegisterController), but it is never invoked, using the default Validator.
Am I forgetting something? I attach part of the code to help understand better my problem.
Validator:
public class AccountValidator implements Validator{
#Override
public boolean supports(Class<?> clazz) {
return (Account.class).isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
//Validation code
}
}
Controller:
#Controller
#RequestMapping(value = "/register")
public class RegisterController {
#Autowired
private AccountValidator accountValidator;
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields("id");
binder.setValidator(accountValidator);
}
#RequestMapping(method = RequestMethod.GET)
#ModelAttribute
public Account register(Locale currentLocale){
Account account = new Account();
return account;
}
#RequestMapping(method = RequestMethod.POST)
public String handleRegister(#Valid #ModelAttribute Account account, BindingResult result){
if(result.hasErrors()){
return "/register";
}
return "home";
}
}
I checked my debug messages in the log, and the initBinder method is being called, but the validation method is never being executed.
Can anyone help me?
I was facing the same issue and i fixed it by declaring the class AccountValidator in context xml file and using #validated in place of #valid.
After going through the source code, the only reason I can find for the WebDataBinder not to invoke your Validator is that your variable is null. This field
#Autowired
private AccountValidator accountValidator;
must be null. I don't know how you got there, Spring would complain if it couldn't autowire a field.
At the moment, I can't tell you why the Validator isn't being called when registered with the WebDataBinder, but here's the workaround:
Get rid of the
binder.setValidator(accountValidator);
and add the the Validator call in the handler method
#RequestMapping(method = RequestMethod.POST)
public String handleRegister(#Valid #ModelAttribute Account account, BindingResult result){
accountValidator.validate(account, result);
if(result.hasErrors()){
return "/register";
}
return "home";
}
Spring will perform default validation (based on your validation provider, ex. Hibernate) and then you apply your custom validation.
I had this same problem and it turned out I was getting an SQL exception, seemingly bypassing my validator because I had my method annotated with #Transactional.
I think you want to use binder.addValidator(accountValidator); instead of binder.setValidator(accountValidator);