Home > front end >  A ConstraintValidator referenced by an annotation is never called although it should work
A ConstraintValidator referenced by an annotation is never called although it should work

Time:03-27

In the debugger, I can see that the annotation somehow works. But the methods initialize(AuthorConstraint) and isValid(Author, ConstraintValidatorContext) of class AuthorConstraintValidator are never called.

I searched for quite a few hours for a solution. But I don’t know what is missing.

Packages: hibernate-core 5.6.5.Final hibernate-validator 6.2.3.Final

Annotation @AuthorConstraint

package myapp.entity.author.constraint;

import javax.validation.Constraint;
import javax.validation.Payload;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * An annotation for special constraints of authors.
 */
@Constraint(validatedBy = {AuthorConstraintValidator.class})
@Retention(RUNTIME)
@Target({TYPE})
public @interface AuthorConstraint {
    String message() default "An author needs either a first name or a last name.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

ConstraintValidator implementation

package myapp.entity.author.constraint;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import myapp.entity.author.Author;

/**
 * A validator for special constraints of authors.
 */
public class AuthorConstraintValidator implements ConstraintValidator<AuthorConstraint, Author> {
    /**
     * {@inheritDoc}
     */
    @Override
    public void initialize(AuthorConstraint constraintAnnotation) {
    }

    @Override
    public boolean isValid(Author author, ConstraintValidatorContext constraintValidatorContext) {
        if (author == null) {
            return true;
        }

        return author.getFirstName() != null || author.getLastName() != null;
    }
}

Author class

package myapp.entity.author;

import myapp.data.DatedEntity;
import myapp.entity.author.constraint.AuthorConstraint;

import javax.persistence.*;
import java.io.Serializable;

/**
 * Represents an author.
 */
@AuthorConstraint
@Entity
@Table(name = "author")
public class Author extends DatedEntity implements Serializable {
    /**
     * ID associated with an author.
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    /**
     * First name of an author.
     */
    @Column(name = "first_name")
    private String firstName;

    /**
     * Last name of an author.
     */
    @Column(name = "last_name")
    private String lastName;

    /**
     * Returns the ID associated with an author.
     *
     * @return Primary key of an author.
     */
    public Integer getId() {
        return id;
    }

    /**
     * Sets the ID of an author.
     *
     * @param id the ID of an author
     */
    @SuppressWarnings({"unused", "SameParameterValue"})
    void setId(Integer id) {
        this.id = id;
    }

    /**
     * Returns the first name of an author.
     *
     * @return First name of an author.
     */
    @SuppressWarnings("unused")
    public String getFirstName() {
        return firstName;
    }

    /**
     * Sets the first name of an author.
     *
     * @param firstName first name
     */
    @SuppressWarnings("unused")
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    /**
     * Returns the last name of an author.
     *
     * @return Last name of an author.
     */
    @SuppressWarnings("unused")
    public String getLastName() {
        return lastName;
    }

    /**
     * Sets the last name of an author.
     *
     * @param lastName last name
     */
    @SuppressWarnings("unused")
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
}

Test code for validator

When I create a validator object myself, I can see that it works as intended.

Author author = new Author();
AuthorConstraintValidator validator = new AuthorConstraintValidator();

validator.initialize(new AuthorConstraint() {
    @Override
    public Class<? extends Annotation> annotationType() {
        return AuthorConstraint.class;
    }

    @Override
    public String message() {
        return null;
    }

    @Override
    public Class<?>[] groups() {
        return new Class[0];
    }

    @Override
    public Class<? extends Payload>[] payload() {
        return new Class[0];
    }
});

System.err.println(validator.isValid(author, null));
// result: false

CodePudding user response:

You are mixing components supporting javax.* annotations with components supporting jakarta.* annotations.

If you're using javax.persistence, you should use javax.validation and thus Hibernate Validator 6.2.x. It's still supported and has the same features as HV 7.0.x (which targets Jakarta EE 9 and thus the jakarta.validation package) and the upcoming HV 8.0.x (which targets Jakarta EE 10).

So switch to Hibernate Validator 6.2 and Bean Validation 2.0.

You should also check your version of the EL implementation. It should be:

  <dependency>
     <groupId>org.glassfish</groupId>
     <artifactId>jakarta.el</artifactId>
     <version>3.0.3</version>
  </dependency>

Make sure you don't have another one around as they go with a ton of different names.

Then if it still doesn't work, one thing is that by default Hibernate ORM does not tell you if something is going wrong when it tries to initialize Hibernate Validator.

You can make it tell you what's going on by setting the following properties to CALLBACK:

  • javax.persistence.validation.group.pre-persist
  • javax.persistence.validation.group.pre-update
  • javax.persistence.validation.group.pre-remove
  • Related