Close

Java 8 Repeating Annotations

[Last Updated: Aug 29, 2018]

Prior to Java 8, using same annotation at the same place (method/field/class etc) is a compile time error. For example, the following code will give compile time error if it is run with any versions between Java 5 to 7 inclusively.

public class UiApplication {
    @Access(Role.ADMIN)
    @Access(Role.SUPER_USER)
    public View getAccountView() {
        return createAccountView();
    }
}
@Retention( RetentionPolicy.RUNTIME )
public @interface Access {
    Role value() default Role.GUEST;
}
public enum Role {
    ADMIN, SUPER_USER, GUEST, ANONYMOUS
}

Here's the error:

    UiApplication.java:6: error: duplicate annotation
    @Access(Role.SUPER_USER)
    ^
    1 error

One way to avoid the above error is; we can change the return type of Access#value to array:

public class UiApplication {
    @Access({Role.ADMIN, Role.SUPER_USER})
    public View getAccountView() {
        return createAccountView();
    }
}
@Retention( RetentionPolicy.RUNTIME )
public @interface Access {
    Role[] value() default Role.GUEST;
}

But what if we have more than one parameter with Access. In that case we can wrap the Access annotation inside another annotation, say MultiAccess.

public class UiApplication {
    @MultiAccess({
            @Access(value = Role.ADMIN, env = "prod"),
            @Access(value = Role.SUPER_USER, env = "test")})
    public View getAccountView() {
        return createAccountView();
    }
}
@Retention( RetentionPolicy.RUNTIME )
public @interface MultiAccess {
    Access[] value() default @Access;
}
@Retention( RetentionPolicy.RUNTIME )
public @interface Access {
   Role value();
   String env();
}

Using Java 8 @Repeatable annotation

In Java 8, we don't have to use the outer annotation @MultiAccess() on our example method UiApplication#getAccountView anymore. We have to, instead, annotate Access annotation class with java.lang.annotation.Repeatable. There will be no change with the definition of the container annotation, MultiAccess.class:

@Access(value = Role.ADMIN, env = "prod")
@Access(value = Role.SUPER_USER, env = "test")
public View getAccountView() {
    return createAccountView();
}
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MultiAccess.class)
public @interface Access {
    Role value() default Role.GUEST;
    String env();
}

For compatibility reasons, repeating annotations are stored in a container annotation during compilation time, which is Syntactically generated. In order for the compiler to do this, two declarations are still required in our code.


Retrieving Annotations by Reflection

There are two ways to retrieve them

  1. Using new methods AnnotatedElement#getAnnotationsByType(Class) or AnnotatedElement#getDeclaredAnnotationsByType(Class)
    for (Access a : UiApplication.class.getMethod("getAccountView")
                   .getDeclaredAnnotationsByType(Access.class)) {
        System.out.println(a.value());
    }
    
  2. Using old methods AnnotatedElement#getDeclaredAnnotation(Class) or AnnotatedElement#getAnnotation(Class). They return a single annotation if one annotation of the requested type is present. If more than one annotation of the requested type is present. they will return null. In latter case, we can still use these methods by first getting their container annotation.
    MultiAccess a = UiApplication.class.getMethod("getAccountView")
            .getDeclaredAnnotation(MultiAccess.class);
    System.out.println(Arrays.toString(a.value()));

That's it. In our examples the difference is minimal. The benefit will be obvious if the annotation has many parameters.

Example Project

Here I'm going to put the above example together, in case you want to download and play with it.

Dependencies and Technologies Used:

  • JDK 1.8
  • Maven 3.0.4

Repeating Annotation Example Select All Download
  • java-repeating-annotation
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • UiApplication.java

    See Also