Search code examples
javaspringspring-bootspring-mvcspring-annotations

apply custom validation annotation to parameter


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.


Solution

  • 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.