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
4
  • 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? Commented Aug 27, 2021 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. Commented Aug 27, 2021 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 Commented Aug 27, 2021 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. Commented Aug 27, 2021 at 23:32

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.