1

Expanding on this question: Create Annotation instance with defaults, in Java
I am trying to add a default annotation instance as a field on the annotation:

import java.lang.annotation.*;
import java.util.function.Supplier;

public class AnnoTester {

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.FIELD, ElementType.TYPE })
    public static @interface Anno {
        Anno DEFAULT = annotation(() -> {
            @Anno
            class Default {}
            return Default.class;
        }, Anno.class);

        String s() default "s";
    }

    @Anno(s = "T")
    public static class TestClass {}

    private static <T extends Annotation> T annotation(Supplier<Class<?>> supplier,
        Class<T> annotationClass) {
        return supplier.get().getAnnotation(annotationClass);
    }

    public static void main(String[] args) {
        System.out.println(Anno.DEFAULT.s()); // NullPointerException if removed
        System.out.println(TestClass.class.getAnnotation(Anno.class).s());
    }
}

Running this gives the desired response of s and T. However if I remove the reference to Anno.DEFAULT in main() the code fails to initialize the annotation (stack trace below).

In this case, the annotation proxy created behind the scenes seems to be missing annotationType().

Is there a way to fix this without moving DEFAULT outside the annotation declaration?

Exception in thread "main" java.lang.ExceptionInInitializerError
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:375)
    at jdk.proxy2/jdk.proxy2.$Proxy1.<clinit>(Unknown Source)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:78)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
    at java.base/java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1044)
    at java.base/java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1030)
    at java.base/sun.reflect.annotation.AnnotationParser$1.run(AnnotationParser.java:306)
    at java.base/sun.reflect.annotation.AnnotationParser$1.run(AnnotationParser.java:304)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:312)
    at java.base/sun.reflect.annotation.AnnotationParser.annotationForMap(AnnotationParser.java:304)
    at java.base/sun.reflect.annotation.AnnotationParser.parseAnnotation2(AnnotationParser.java:294)
    at java.base/sun.reflect.annotation.AnnotationParser.parseAnnotations2(AnnotationParser.java:121)
    at java.base/sun.reflect.annotation.AnnotationParser.parseAnnotations(AnnotationParser.java:73)
    at java.base/java.lang.Class.createAnnotationData(Class.java:3997)
    at java.base/java.lang.Class.annotationData(Class.java:3986)
    at java.base/java.lang.Class.getAnnotation(Class.java:3869)
    at AnnoTester.main(AnnoTester.java:30)
Caused by: java.lang.NullPointerException: Cannot invoke "java.lang.reflect.Method.getName()" because "method" is null
    at java.base/sun.reflect.annotation.AnnotationInvocationHandler.invoke(AnnotationInvocationHandler.java:61)
    at jdk.proxy2/jdk.proxy2.$Proxy1.annotationType(Unknown Source)
    at java.base/sun.reflect.annotation.AnnotationParser.parseAnnotations2(AnnotationParser.java:123)
    at java.base/sun.reflect.annotation.AnnotationParser.parseAnnotations(AnnotationParser.java:73)
    at java.base/java.lang.Class.createAnnotationData(Class.java:3997)
    at java.base/java.lang.Class.annotationData(Class.java:3986)
    at java.base/java.lang.Class.getAnnotation(Class.java:3869)
    at AnnoTester.annotation(AnnoTester.java:25)
    at AnnoTester$Anno.<clinit>(AnnoTester.java:11)
    ... 21 more
boot-and-bonnet
  • 731
  • 2
  • 5
  • 16
  • 1
    There’s some pretty complex interactions regarding class initialization here, which feels like a code smell to me. Is it really necessary to keep `DEFAULT` inside Anno? – VGR Aug 27 '21 at 14:20
  • In general, keeping constants related to a type within that type has benefits. No need for pseudo-namespace naming, and harder to miss when refactoring the main type, such as a move or rename. – boot-and-bonnet Aug 27 '21 at 17:37
  • My fallback is to have a non-instantiable nested class implementing the annotation with a static final reference to its annotation `Anno.Default.INSTANCE` – boot-and-bonnet Aug 27 '21 at 17:43
  • I agree that the constants should be with the type to which they pertain, but this is a special case. I think your fallback is the best alternative. It’s still pretty readable and shouldn’t present a challenge during refactoring. – VGR Aug 27 '21 at 23:32

0 Answers0