Changing Annotation Parameters At Runtime

1. Overview

Annotations, a form of metadata which you can add to Java code. These annotations can be processed at compile time and embedded to class files or can be retained and accessed at runtime using Reflection.

In this article, we will discuss how to change annotation value at runtime using Reflection. We will use class-level annotation for this example.

2. Annotation

Java allows creating new annotations using existing ones. In the simplest form, an annotation is represented as @ symbol followed by annotation name:

@Override

Let’s create our own annotation Greeter:

@Retention(RetentionPolicy.RUNTIME)
public @interface Greeter {    
    public String greet() default ""; 
}

Now, we will be creating a Java class Greetings which uses the class-level annotation:

@Greeter(greet="Good morning")
public class Greetings {}

Now, we will access annotation value using reflection. Java class Class provides a method getAnnotation to access annotations of a class:

Greeter greetings = Greetings.class.getAnnotation(Greeter.class);
System.out.println("Hello there, " + greetings.greet() + " !!");

3. Alter Annotation

Java class Class maintains a map for managing annotations – Annotation class as keys and Annotation object as value:

Map<Class<? extends Annotation>, Annotation> map;

We will update this map to alter annotation at runtime. Approach to access this map differs in various JDK implementation. We will discuss it for JDK7 and JDK8.

3.1. JDK 7 Implementation

Java class Class has field annotations. As this is a private field, to access it, we have to set accessibility of the field to true. Java provides method getDeclaredField to access any field by its name:

Field annotations = Class.class.getDeclaredField(ANNOTATIONS);
annotations.setAccessible(true);

Now, let’s get access to annotation map for class Greeter:

 Map<Class<? extends Annotation>, Annotation> map = annotations.get(targetClass);

Now, this is the map which contains information about all annotations and their value object. We want to alter Greeter annotation value which we can achieve by updating annotation object of Greeter class:

map.put(targetAnnotation, targetValue);

3.2. JDK 8 Implementation

Java 8 implementations stores annotations information inside a class AnnotationData. We can access this object using the annotationData method. We will set accessibility for the annotationData method to true as it is a private method:

Method method = Class.class.getDeclaredMethod(ANNOTATION_METHOD, null);
method.setAccessible(true);

Now, we can access annotations field. As this field also a private field, we will set accessibility to true:

Field annotations = annotationData.getClass().getDeclaredField(ANNOTATIONS);
annotations.setAccessible(true);

This field has annotations cache map which stores annotation class and value object. Let’s alter that:

Map<Class<? extends Annotation>, Annotation> map = annotations.get(annotationData); 
map.put(targetAnnotation, targetValue);

4. Application

Let’s take this example:

Greeter greetings = Greetings.class.getAnnotation(Greeter.class);
System.err.println("Hello there, " + greetings.greet() + " !!");

This will be greeting “Good morning” as that is the value we provided to annotation.
Now, we will create one more object of Greeter type with value as “Good evening”:

Greeter targetValue = new DynamicGreeter("Good evening");

Let’s update the annotation map with the new value:

alterAnnotationValueJDK8(Greetings.class, Greeter.class, targetValue);

Let’s check greeting value again:

greetings = Greetings.class.getAnnotation(Greeter.class);
System.err.println("Hello there, " + greetings.greet() + " !!");

It will greet as “Good evening”.

5. Conclusion

Java implementations use two data field to store annotation data: annotationsdeclaredAnnotations. The difference between these two: first store annotations from parent classes also and later one stores only for current class.

As the implementation of getAnnotation differs in JDK 7 and JDK 8, we using here annotations field map for simplicity.

And, as always, the source code of implementation is available over on Github.