Search code examples
javavalidationannotationsconstraintsbean-validation

Using validator.getConstraintsForClass on an annotation gives different result than using getConstraintsForProperty containing the annotation


I have a number of Java Annotation classes.

The idea is that developers/consumers of my project will use these annotations on fields inside their POJOs in order to describe each field.

Example relationship between these annotations: @CustomString -> @CustomPassword

Why is it that when I try to get the constraints on @CustomPassword using validator.getConstraintsForClass, the min does not contain the value specified inside CustomPassword i.e 10

I want to use:

@OverridesAttribute(constraint = CustomString.class, name = "min")
int min() default 10;

To override the value because I'd like developers to be able to create their own annotation classes that sit on top of CustomPassword and maybe provide their own overrides.

@CustomString sets @Size min and max constraints:

import java.lang.annotation.*;
import javax.validation.Constraint;
import javax.validation.OverridesAttribute;
import javax.validation.Payload;
import javax.validation.constraints.Size;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Size
@Constraint(validatedBy = {})
public @interface CustomString {

  @OverridesAttribute(constraint = Size.class, name = "min")
  int min() default 0;

  @OverridesAttribute(constraint = Size.class, name = "max")
  int max() default Integer.MAX_VALUE;

  Class<?>[] groups() default {};

  Class<? extends Payload>[] payload() default {};

  String message() default "{org.example.annotation.base.string}";
}

@CustomPassword which contains a reference to @CustomString, and attempts to override the min from @CustomString:

import org.ecample.annotation.base.CustomString;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.OverridesAttribute;
import javax.validation.Payload;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

@CustomString
@Constraint(validatedBy = {})
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface CustomPassword {

  @OverridesAttribute(constraint = CustomString.class, name = "min")
  int min() default 10;

  Class<?>[] groups() default {};

  Class<? extends Payload>[] payload() default {};

  String message() default "{org.example.annotation.extended.password}";
}

Why is it that when I run:

Class<?> klass = CustomPassword.class;
Validator validator =
        Validation.byDefaultProvider()
            .configure()
            .messageInterpolator(new ParameterMessageInterpolator())
            .buildValidatorFactory().getValidator();
BeanDescriptor beanDescriptor = validator.getConstraintsForClass(klass);

The constraintDescriptors inside BeanDescriptor still has a min of 0, shouldn't it have been overridden by the min inside CustomPassword and be set to 10?

((BeanDescriptorImpl)beanDescriptor).constraintDescriptors.iterator().next().annotationDescriptor

@c.c.c.m.t.a.b.CustomString(groups=[Ljava.lang.Class;@3d9f0a5, max=2147483647, message={org.example.annotation.base.string}, min=0, payload=[Ljava.lang.Class;@1953bc95)

Solution

  • The problem is that you are trying to look at the bean descriptor of a constraint annotation itself. The Validator#getConstraintsForClass(..) is intended to provide the metadata for the bean. If you'd try something like:

    public class ConstrainedBean {
        @CustomPassword
        public String password;
    }
    //...
    
    BeanDescriptor beanDescriptor = validator.getConstraintsForClass( ConstrainedBean.class );
    

    This bean descriptor will contain a constrained property and you'd be able to get the expected 10 navigating through constraintDescriptor#composingConstraints first to get to CustomString and then to Size.

    When you are passing the CustomPassword.class you are getting a metadata as if you are going to validate the instance of a CustomPassword.class and at that point validator considers CustomString as a class-level constraint and none of OverridesAttribute from CustomPassword are considered since beans do not override any constraint attributes.