Home > database >  apply custom validation annotation to parameter
apply custom validation annotation to parameter

Time:09-29

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.

CodePudding user response:

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.

CodePudding user response:

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.

  • Related