Search code examples
javaannotationsreflect

Is it possible to know whether an Annotation Method is overriden (boolean values)?


I have tried a lot of things online but nothing seem to work for me. I want to know whether an annotation method has been @Overriden (either with the same value as its default).

Take a look at this example:

public class AnnoTest {

    @Anno
    private String something;

    public static void main(String[] args) throws NoSuchFieldException, SecurityException, NoSuchMethodException {
        Field field = AnnoTest.class.getDeclaredField("something");
        field.setAccessible(true);
        boolean isDefault= field.getAnnotation(Anno.class).annotationType().getDeclaredMethod("include").isDefault();
        System.out.println(isDefault); //returns false

    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.FIELD })
    public @interface Anno {
        boolean include() default false;
    }
}

For some reason it returns false. When i change it to:

@Anno(include = false)
private String something;

It returns false again. Is there a way to know whether the value has been declared in the annotation?

I know i could just compare the default value and its current value, but it will not work for me. I want to know if it has been declared.


With other words I need some kind of magic boolean that does the following:

@Anno
private String something;

return false.

@Anno(include = true)
private String something;

return true.

@Anno(include = false)
private String something;

return true.


The reason of this is that i am wishing to add a method (to my annotation) named "parent". When a parent (a String) has been declared the annotation, this field will inherit the annotation of the field named parent. Take a look at this example:

public class AnnoTest {

    @Anno(include = false)
    private Something something = new Something();

    @Anno(parent = "something")
    private Something somethingElse  = new Something();

    public static void main(String[] args) throws NoSuchFieldException, SecurityException, NoSuchMethodException {
        AnnoTest test = new AnnoTest();

        Field somethingField = AnnoTest.class.getDeclaredField("something");
        somethingField.setAccessible(true);

        Field somethingElseField = AnnoTest.class.getDeclaredField("somethingElse");
        somethingField.setAccessible(true);

        Anno anno = somethingElseField.getAnnotation(Anno.class);

        if (anno.parent().equals("something")) {
            boolean include = somethingField.getAnnotation(Anno.class).include();
            test.somethingElse.isIncluded = include;
        }

        //If not declared it will return true, which it should be false, because "something" field has it false.
        boolean include = somethingElseField.getAnnotation(Anno.class).include();
        //if somethingElse has declared "include", dominate the value, else keep it from the parent
        test.somethingElse.isIncluded = include;

    }

    public class Something {
        boolean isIncluded;
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.FIELD })
    public @interface Anno {
        boolean include() default false;

        String parent() default "";
    }
}

Solution

  • The reflection api does not allow to query whether an annotation value has been specified explicitly or merely defaulted.

    The usual workaround is to specify a default value that nobody in their right mind would specify explicitly, and check for that value instead. For instance, JPA uses "" for that purpose.

    One might try

    Boolean value() default null;
    

    but as you rightly point out in the comments, java does not support Boolean annotation values, only boolean ones. You could use an enum with 3 values instead, but that's probably burdensome for your users.

    That leaves dark magic: You could parse the classfile yourself. This would work, because the classfile only lists specified annotation attributes, as the following javap output shows:

    Given

    @Anno(false)
    public void foo() 
    

    we get

    Constant pool:
        ...
        #16 = Utf8               Lstackoverflow/Anno;
        #17 = Utf8               value
        #18 = Integer            0
    
      public void foo();
        descriptor: ()V
        flags: ACC_PUBLIC
        RuntimeVisibleAnnotations:
          0: #16(#17=Z#18)
    

    but given

    @Anno()
    public void foo() {
    

    we get

    Constant pool:
      ...
      #16 = Utf8               Lstackoverflow/Anno;
    
    public void foo();
      descriptor: ()V
      flags: ACC_PUBLIC
      RuntimeVisibleAnnotations:
        0: #16()
    

    That said, even if you manage to do this, you might surprise your users and confuse their tooling. For instance, it's quite possible that an IDE will flag explicit assignments of a default value as redundant.

    If at all possible, I'd therefore change your annotation so you don't have to distinguish whether a boolean has been specified explicitly.