Search code examples
javaspring-bootspring-data-jpaspring-validator

Custom ConstraintValidator not working (Exception)


I have a problem with Spring, in particular with ConstraintValidator. I want to release custom validation for field contains email. It must be unique. OK. The task is clear, I do it like this:

UniqueEmail.java

@Constraint(validatedBy = UniqueEmailValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface UniqueEmail {
    String message() default "Invalid phone number";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

UniqueEmailValidator.java

public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {

    private final UserRepository userRepository;

    public UniqueEmailValidator(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public boolean isValid(String email, ConstraintValidatorContext context) {
        return email != null && !userRepository.findByEmail(email).isPresent();
    }
}

User.java

@Data
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true)
    @NotEmpty(message = "Email should not be empty")
    @Email(message = "Email should be valid")
    @UniqueEmail(message = "Email address is already registered")
    private String email;

    @NotEmpty(message = "Password should not be empty")
    private String password;

    @NotEmpty(message = "Name should not be empty")
    @Size(min = 2, max = 30, message = "Name should be between 2 and 30 characters")
    private String username;

    private boolean enabled = true;

    @Enumerated(value = EnumType.STRING)
    private Role role;
}

UserRepository.java

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

AuthController.java

@Controller
@RequestMapping("/auth")
public class AuthController {

    private final UserRepository userRepository;

    private final PasswordEncoder passwordEncoder;

    @Autowired
    public AuthController(UserRepository userRepository, PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    @GetMapping("/login")
    public String login(Principal principal) {
        if(principal != null)
            return "redirect:/";
        return "auth/login";
    }

    @GetMapping("/register")
    public String register(@ModelAttribute("user") User user, Principal principal) {
        if(principal != null)
            return "redirect:/";
        return "auth/register";
    }

    @PostMapping("/register")
    public String newCustomer(Principal principal, Model model, @ModelAttribute("user") @Valid User user, BindingResult bindingResult) {
        if(principal != null)
            return "redirect:/";

        if(bindingResult.hasErrors())
            return "auth/register";

        user.setPassword(passwordEncoder.encode(user.getPassword()));
        user.setRole(Role.CUSTOMER);
        userRepository.saveAndFlush(user);

        model.addAttribute("success", true);
        model.addAttribute("user", new User());

        return "auth/register";
    }
}

If i try input existing email everything works fine (got message "Email address is already registered"). But if I try input a new email, I get an error "Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is javax.validation.ValidationException: HV000064: Unable to instantiate ConstraintValidator: bla.bla.bla.validator.UniqueEmailValidator.] with root cause".

I'm trying do with @Component and @Autowired, but got the same result. I'm trying do with noArgs constructor and got NullPointerException (UserRepository not injected).

Why? I don'n understand.


Solution

  • Solved the problem in this way. I understand that this is a crutch, but I have not yet found another solution.

    @Component
    public class UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String> {
    
        private UserRepository userRepository;
    
        public UniqueEmailValidator() {
        }
    
        @Autowired
        public UniqueEmailValidator(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    
        @Override
        public boolean isValid(String email, ConstraintValidatorContext context) {
            if(userRepository == null)
                return true;
            return email != null && !userRepository.findByEmail(email).isPresent();
        }
    }
    

    Added noArgs Constructor, and in isValid function added ckeck if null userRepository.