Adding Java Annotations at Runtime Adding Java Annotations at Runtime java java

Adding Java Annotations at Runtime


It's possible via bytecode instrumentation library such as Javassist.

In particular, take a look at AnnotationsAttribute class for an example on how to create / set annotations and tutorial section on bytecode API for general guidelines on how to manipulate class files.

This is anything but simple and straightforward, though - I would NOT recommend this approach and suggest you consider Tom's answer instead unless you need to do this for a huge number of classes (or said classes aren't available to you until runtime and thus writing an adapter is impossible).


It is also possible to add an Annotation to a Java class at runtime using the Java reflection API. Essentially one must recreate the internal Annotation maps defined in the class java.lang.Class (or for Java 8 defined in the internal class java.lang.Class.AnnotationData). Naturally this approach is quite hacky and might break at any time for newer Java versions. But for quick and dirty testing/prototyping this approach can be useful at times.

Proove of concept example for Java 8:

public final class RuntimeAnnotations {    private static final Constructor<?> AnnotationInvocationHandler_constructor;    private static final Constructor<?> AnnotationData_constructor;    private static final Method Class_annotationData;    private static final Field Class_classRedefinedCount;    private static final Field AnnotationData_annotations;    private static final Field AnnotationData_declaredAnotations;    private static final Method Atomic_casAnnotationData;    private static final Class<?> Atomic_class;    static{        // static initialization of necessary reflection Objects        try {            Class<?> AnnotationInvocationHandler_class = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");            AnnotationInvocationHandler_constructor = AnnotationInvocationHandler_class.getDeclaredConstructor(new Class[]{Class.class, Map.class});            AnnotationInvocationHandler_constructor.setAccessible(true);            Atomic_class = Class.forName("java.lang.Class$Atomic");            Class<?> AnnotationData_class = Class.forName("java.lang.Class$AnnotationData");            AnnotationData_constructor = AnnotationData_class.getDeclaredConstructor(new Class[]{Map.class, Map.class, int.class});            AnnotationData_constructor.setAccessible(true);            Class_annotationData = Class.class.getDeclaredMethod("annotationData");            Class_annotationData.setAccessible(true);            Class_classRedefinedCount= Class.class.getDeclaredField("classRedefinedCount");            Class_classRedefinedCount.setAccessible(true);            AnnotationData_annotations = AnnotationData_class.getDeclaredField("annotations");            AnnotationData_annotations.setAccessible(true);            AnnotationData_declaredAnotations = AnnotationData_class.getDeclaredField("declaredAnnotations");            AnnotationData_declaredAnotations.setAccessible(true);            Atomic_casAnnotationData = Atomic_class.getDeclaredMethod("casAnnotationData", Class.class, AnnotationData_class, AnnotationData_class);            Atomic_casAnnotationData.setAccessible(true);        } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | NoSuchFieldException e) {            throw new IllegalStateException(e);        }    }    public static <T extends Annotation> void putAnnotation(Class<?> c, Class<T> annotationClass, Map<String, Object> valuesMap){        putAnnotation(c, annotationClass, annotationForMap(annotationClass, valuesMap));    }    public static <T extends Annotation> void putAnnotation(Class<?> c, Class<T> annotationClass, T annotation){        try {            while (true) { // retry loop                int classRedefinedCount = Class_classRedefinedCount.getInt(c);                Object /*AnnotationData*/ annotationData = Class_annotationData.invoke(c);                // null or stale annotationData -> optimistically create new instance                Object newAnnotationData = createAnnotationData(c, annotationData, annotationClass, annotation, classRedefinedCount);                // try to install it                if ((boolean) Atomic_casAnnotationData.invoke(Atomic_class, c, annotationData, newAnnotationData)) {                    // successfully installed new AnnotationData                    break;                }            }        } catch(IllegalArgumentException | IllegalAccessException | InvocationTargetException | InstantiationException e){            throw new IllegalStateException(e);        }    }    @SuppressWarnings("unchecked")    private static <T extends Annotation> Object /*AnnotationData*/ createAnnotationData(Class<?> c, Object /*AnnotationData*/ annotationData, Class<T> annotationClass, T annotation, int classRedefinedCount) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {        Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) AnnotationData_annotations.get(annotationData);        Map<Class<? extends Annotation>, Annotation> declaredAnnotations= (Map<Class<? extends Annotation>, Annotation>) AnnotationData_declaredAnotations.get(annotationData);        Map<Class<? extends Annotation>, Annotation> newDeclaredAnnotations = new LinkedHashMap<>(annotations);        newDeclaredAnnotations.put(annotationClass, annotation);        Map<Class<? extends Annotation>, Annotation> newAnnotations ;        if (declaredAnnotations == annotations) {            newAnnotations = newDeclaredAnnotations;        } else{            newAnnotations = new LinkedHashMap<>(annotations);            newAnnotations.put(annotationClass, annotation);        }        return AnnotationData_constructor.newInstance(newAnnotations, newDeclaredAnnotations, classRedefinedCount);    }    @SuppressWarnings("unchecked")    public static <T extends Annotation> T annotationForMap(final Class<T> annotationClass, final Map<String, Object> valuesMap){        return (T)AccessController.doPrivileged(new PrivilegedAction<Annotation>(){            public Annotation run(){                InvocationHandler handler;                try {                    handler = (InvocationHandler) AnnotationInvocationHandler_constructor.newInstance(annotationClass,new HashMap<>(valuesMap));                } catch (InstantiationException | IllegalAccessException                        | IllegalArgumentException | InvocationTargetException e) {                    throw new IllegalStateException(e);                }                return (Annotation)Proxy.newProxyInstance(annotationClass.getClassLoader(), new Class[] { annotationClass }, handler);            }        });    }}

Usage example:

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface TestAnnotation {    String value();}public static class TestClass{}public static void main(String[] args) {    TestAnnotation annotation = TestClass.class.getAnnotation(TestAnnotation.class);    System.out.println("TestClass annotation before:" + annotation);    Map<String, Object> valuesMap = new HashMap<>();    valuesMap.put("value", "some String");    RuntimeAnnotations.putAnnotation(TestClass.class, TestAnnotation.class, valuesMap);    annotation = TestClass.class.getAnnotation(TestAnnotation.class);    System.out.println("TestClass annotation after:" + annotation);}

Output:

TestClass annotation before:nullTestClass annotation after:@RuntimeAnnotations$TestAnnotation(value=some String)

Limitations of this approach:

  • New versions of Java may break the code at any time.
  • The above example only works for Java 8 - making it work for older Java versions would require checking the Java version at runtime and changing the implementation accordingly.
  • If the annotated Class gets redefined (e.g. during debugging), the annotation will be lost.
  • Not thoroughly tested; not sure if there are any bad side effects - use at your own risk...


It's not possible to add an annotation at runtime, it sounds like you need to introduce an adapter that module B uses to wrap the object from module A exposing the required annotated methods.