65

Imagine there is a class:

@Something(someProperty = "some value")
public class Foobar {
    //...
}

Which is already compiled (I cannot control the source), and is part of the classpath when the jvm starts up. I would like to be able to change "some value" to something else at runtime, such that any reflection thereafter would have my new value instead of the default "some value".

Is this possible? If so, how?

Richard Pianka
  • 3,317
  • 2
  • 28
  • 36
  • 1
    Class has an `annotations` and a `declaredAnnotations` map fields that you can try to modify with reflection... – assylias Jan 10 '13 at 23:20
  • 4
    http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/Class.java around lines 3086. This is very fragile because there might be side effects and if the implementation of Class.java changes it could stop working... – assylias Jan 10 '13 at 23:24
  • 1
    Oh that's cool, so you're saying basically just swap out (or stick in beforehand) my own instance of the annotation, with the value I want? – Richard Pianka Jan 11 '13 at 00:08
  • 3
    @assylias It’s fragile even without changing the implementation, due to the use of `SoftReference`, which makes it possible to lose all your changes at arbitrary points. – Holger Sep 07 '18 at 11:19

7 Answers7

50

Warning: Not tested on OSX - see comment from @Marcel

Tested on OSX. Works fine.

Since I also had the need to change annotation values at runtime, I revisited this question.

Here is a modified version of @assylias approach (many thanks for the inspiration).

/**
 * Changes the annotation value for the given key of the given annotation to newValue and returns
 * the previous value.
 */
@SuppressWarnings("unchecked")
public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue){
    Object handler = Proxy.getInvocationHandler(annotation);
    Field f;
    try {
        f = handler.getClass().getDeclaredField("memberValues");
    } catch (NoSuchFieldException | SecurityException e) {
        throw new IllegalStateException(e);
    }
    f.setAccessible(true);
    Map<String, Object> memberValues;
    try {
        memberValues = (Map<String, Object>) f.get(handler);
    } catch (IllegalArgumentException | IllegalAccessException e) {
        throw new IllegalStateException(e);
    }
    Object oldValue = memberValues.get(key);
    if (oldValue == null || oldValue.getClass() != newValue.getClass()) {
        throw new IllegalArgumentException();
    }
    memberValues.put(key,newValue);
    return oldValue;
}

Usage example:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassAnnotation {
  String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldAnnotation {
  String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodAnnotation {
  String value() default "";
}
@ClassAnnotation("class test")
public static class TestClass{
    @FieldAnnotation("field test")
    public Object field;
    @MethodAnnotation("method test")
    public void method(){

    }
}

public static void main(String[] args) throws Exception {
    final ClassAnnotation classAnnotation = TestClass.class.getAnnotation(ClassAnnotation.class);
    System.out.println("old ClassAnnotation = " + classAnnotation.value());
    changeAnnotationValue(classAnnotation, "value", "another class annotation value");
    System.out.println("modified ClassAnnotation = " + classAnnotation.value());

    Field field = TestClass.class.getField("field");
    final FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
    System.out.println("old FieldAnnotation = " + fieldAnnotation.value());
    changeAnnotationValue(fieldAnnotation, "value", "another field annotation value");
    System.out.println("modified FieldAnnotation = " + fieldAnnotation.value());

    Method method = TestClass.class.getMethod("method");
    final MethodAnnotation methodAnnotation = method.getAnnotation(MethodAnnotation.class);
    System.out.println("old MethodAnnotation = " + methodAnnotation.value());
    changeAnnotationValue(methodAnnotation, "value", "another method annotation value");
    System.out.println("modified MethodAnnotation = " + methodAnnotation.value());
}

The advantage of this approach is, that one does not need to create a new annotation instance. Therefore one doesn't need to know the concrete annotation class in advance. Also the side effects should be minimal since the original annotation instance stays untouched.

Tested with Java 8.

Nicholas Terry
  • 1,812
  • 24
  • 40
Balder
  • 8,623
  • 4
  • 39
  • 61
  • 1
    Thanks Balder, Can you please explain in line `f = handler.getClass().getDeclaredField("memberValues");` what is memberValues here? – MANISH PATHAK Apr 05 '15 at 16:08
  • 2
    "memberValues" is the private Map of a Java Annotation, where it's member-value-pairs are stored - i.e. here any value of an annotation is stored as a pair of its name/key and its acutal value. The method above simply accesses this field using reflection by setting it accessible and then replaces the existing value with the given new value. – Balder Apr 13 '15 at 05:30
  • Can somebody tell me how to change annotation on field? This solution works filne for class annotations but not for the fields :( – Antoniossss May 18 '15 at 10:05
  • I just tested this and for me this approach also works with field and method Annotations - I added an example that shows this. Can you explain in more detail where you have problems? – Balder May 18 '15 at 12:36
  • @Balder: Fantastic solution, if I could give you ten votes I would! Also worked well with Java 7. – chrisg Jun 03 '15 at 09:50
  • I think it is wrong. The question was "...something else at runtime, such that any reflection thereafter would have my new value..." . The problem is that getMethod only returns you a copy of a method so you effectively change data only on a copy. This is why your test works so far but it will fail if you call getMethod(..) again, try TestClass.class.getMethod("method").getAnnotation(MethodAnnotation.class).value() and you will see the old value. (tested on osx, Jdk 8_25) – Marcel Aug 19 '15 at 07:34
  • @Marcel I just tested your example and it certainly does work on my computer - Windows 7 JDK 8_60. Perhaps the JRE implementation is different on osx (not tested this myself)... – Balder Aug 22 '15 at 13:16
  • Anybody tries this with Junit and org.databene.contiperf.PerfTest and Java 1.7? I can change my annotation but my method still gets old value. Anybody knows why? – Kacu Oct 23 '15 at 14:49
  • Hi @Balder. I would like to use your solution to change a WebService targetNamespace at runtime in a way that whenever you enter http://baseurl/Something?wsdl you get the new value. Where or when should I call the changeAnnotationValue() for this to happen? Thanks. – edmond Feb 25 '16 at 16:04
  • 3
    @Marcel: you get a copy when querying a class for a `Method` (or any other member), but the copy is initialized with the cached information, if it exist. However, this cached information is softly referenced and can be dropped at any time. So it’s basically a matter of luck, whether subsequent reflective queries will get the manipulated annotation or will reconstruct the original. – Holger Sep 29 '16 at 13:51
  • 1
    I tried this solution. But its changing the annotation value for all instances of the class. Is it possible to change only for specific instance of class? – Sarath Sep 30 '16 at 14:52
  • final ClassAnnotation classAnnotation = TestClass.class.getAnnotation(ClassAnnotation.class); where is ClassAnnotation and TestClass.class – Shweta Nandha Sep 04 '17 at 05:55
  • It isn't working for me, the handler is returning the fields of the whole annotation class and not my annotation. – Bahij.Mik Mar 13 '20 at 20:11
  • Doesn't work for me on Android. Has someone else tried it yet? The InvocationHandler is of type libcore.reflect.AnnotationFactory and does not have the memberValues field. However, it has an array field called elements that contains the values set in the annotation. I wanted to edit it, but even writing getDeclaredField("elements") causes a NoSuchFieldException. Any ideas? Edit: I created a dedicated thread for this question: https://stackoverflow.com/questions/63429688/modify-a-method-definitions-annotation-string-parameter-at-runtime-on-android – Nico Feulner Aug 16 '20 at 00:03
  • This solution worked fine for me, unlike assylias' which did not. You did a better job of giving examples, too.! After beating my head against this problem for two days, it was so nice to find your solution! – Tihamer Feb 09 '21 at 01:27
44

This code does more or less what you ask for - it is a simple proof of concept:

  • a proper implementation needs to also deal with the declaredAnnotations
  • if the implementation of annotations in Class.java changes, the code will break (i.e. it can break at any time in the future)
  • I have no idea if there are side effects...

Output:

oldAnnotation = some value
modifiedAnnotation = another value

public static void main(String[] args) throws Exception {
    final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
    System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
    Annotation newAnnotation = new Something() {

        @Override
        public String someProperty() {
            return "another value";
        }

        @Override
        public Class<? extends Annotation> annotationType() {
            return oldAnnotation.annotationType();
        }
    };
    Field field = Class.class.getDeclaredField("annotations");
    field.setAccessible(true);
    Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) field.get(Foobar.class);
    annotations.put(Something.class, newAnnotation);

    Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
    System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
}

@Something(someProperty = "some value")
public static class Foobar {
}

@Retention(RetentionPolicy.RUNTIME)
@interface Something {

    String someProperty();
}
assylias
  • 321,522
  • 82
  • 660
  • 783
  • Apologies for raising this from the dead but I have a question. I am modifying some annotation values in a test mock based on a fixture builder type pattern. Sometimes the test fails to update the annotation value and bombs out with "Class cast exception" whereas immediately after everything runs OK (green). Any advice? I'm using JUnit4 under a Robolectric-Android environment... – BrantApps Dec 03 '13 at 18:00
  • 1
    @OceanLife Without seeing the test in question it is difficult to tell - it might be due to the interaction with the mocking framework which itself uses reflection or changes the bytecode. You should probably post a separate question with additional details. – assylias Dec 03 '13 at 23:05
  • @assylias Thankyou for your prompt response. There is some consensus regarding CGLIB and mocking framework affecting this approach. I've concluded that this method isn't the best route for my tests as it stands but +1 for the cool code. Thanks. – BrantApps Dec 03 '13 at 23:58
  • 22
    this does not work with java 8. `getDeclaredField("annotations")` seems not to work anymore – Zarathustra Jul 03 '14 at 13:44
  • Also note that according to the Javadoc of Annotation, one should also overwrite the toString, hashCode and equals methods and forward them to the `oldAnnotation`... – Balder Jan 23 '15 at 19:51
  • Can somebody tell me how to change annotation on field? This solution works filne for class annotations but not for the fields :( – Antoniossss May 18 '15 at 11:10
  • 5
    This article shows how to manipulate the annotations in Java 8: https://rationaleemotions.wordpress.com/2016/05/27/changing-annotation-values-at-runtime/. Look for "public class AnnotationHelper" at about the middle of the page. – mvermand Jan 05 '17 at 07:40
3

This one works on my machine with Java 8. It changes the value of ignoreUnknown in the annotation @JsonIgnoreProperties(ignoreUnknown = true) from true to false.

final List<Annotation> matchedAnnotation = Arrays.stream(SomeClass.class.getAnnotations()).filter(annotation -> annotation.annotationType().equals(JsonIgnoreProperties.class)).collect(Collectors.toList());    

final Annotation modifiedAnnotation = new JsonIgnoreProperties() {
    @Override public Class<? extends Annotation> annotationType() {
        return matchedAnnotation.get(0).annotationType();
    }    @Override public String[] value() {
        return new String[0];
    }    @Override public boolean ignoreUnknown() {
        return false;
    }    @Override public boolean allowGetters() {
        return false;
    }    @Override public boolean allowSetters() {
        return false;
    }
};    

final Method method = Class.class.getDeclaredMethod("getDeclaredAnnotationMap", null);
method.setAccessible(true);
final Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) method.invoke(SomeClass.class, null);
annotations.put(JsonIgnoreProperties.class, modifiedAnnotation);
Stefano Palazzo
  • 4,212
  • 2
  • 29
  • 40
Patze
  • 297
  • 2
  • 13
3

SPRING can do this job very easily , might be useful for spring developer . follow these steps :-

First Solution :- 1)create a Bean returning a value for someProperty . Here I injected the somePropertyValue with @Value annotation from DB or property file :-

    @Value("${config.somePropertyValue}")
    private String somePropertyValue;

    @Bean
    public String somePropertyValue(){
        return somePropertyValue;
    }

2)After this , it is possible to inject the somePropertyValue into the @Something annotation like this :-

@Something(someProperty = "#{@somePropertyValue}")
public class Foobar {
    //...
}

Second solution :-

1) create getter setter in bean :-

 @Component
    public class config{
         @Value("${config.somePropertyValue}")
         private String somePropertyValue;

         public String getSomePropertyValue() {
           return somePropertyValue;
         }
        public void setSomePropertyValue(String somePropertyValue) {
           this.somePropertyValue = somePropertyValue;
        }
    }

2)After this , it is possible to inject the somePropertyValue into the @Something annotation like this :-

@Something(someProperty = "#{config.somePropertyValue}")
public class Foobar {
    //...
}
Vijay
  • 4,694
  • 1
  • 30
  • 38
1

Try this solution for Java 8

public static void main(String[] args) throws Exception {
    final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
    System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
    Annotation newAnnotation = new Something() {

        @Override
        public String someProperty() {
            return "another value";
        }

        @Override
        public Class<? extends Annotation> annotationType() {
            return oldAnnotation.annotationType();
        }
    };
    Method method = Class.class.getDeclaredMethod("annotationData", null);
    method.setAccessible(true);
    Object annotationData = method.invoke(getClass(), null);
    Field declaredAnnotations = annotationData.getClass().getDeclaredField("declaredAnnotations");
    declaredAnnotations.setAccessible(true);
    Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) declaredAnnotations.get(annotationData);
    annotations.put(Something.class, newAnnotation);

    Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
    System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
}

@Something(someProperty = "some value")
public static class Foobar {
}

@Retention(RetentionPolicy.RUNTIME)
@interface Something {
    String someProperty();
}
0

i am able to access and modify annotaions in this way in jdk1.8,but not sure why has no effect,

try {
    Field annotationDataField = myObject.getClass().getClass().getDeclaredField("annotationData");
    annotationDataField.setAccessible(true);
    Field annotationsField = annotationDataField.get(myObject.getClass()).getClass().getDeclaredField("annotations");
    annotationsField.setAccessible(true);
    Map<Class<? extends Annotation>, Annotation> annotations =  (Map<Class<? extends Annotation>, Annotation>) annotationsField.get(annotationDataField.get(myObject.getClass()));
    annotations.put(Something.class, newSomethingValue);
} catch (IllegalArgumentException | IllegalAccessException e) {
    e.printStackTrace();
} catch (NoSuchFieldException e) {
    e.printStackTrace();
} catch (SecurityException e) {    
    e.printStackTrace();
}
-4

Annotation attribute values have to be constants - so unless you want to do some serious byte code manipulation it won't be possible. Is there a cleaner way, such as creating a wrapper class with the annotation you desire?

Tom McIntyre
  • 3,620
  • 2
  • 23
  • 32
  • Nope, it'll have to be this one unfortunately. How difficult / crazy is it to modify constants at runtime? Does the jvm do some sort of string interning in the heap, or is it more of an inline, literally in the program text part of the bytecode? – Richard Pianka Jan 10 '13 at 23:18
  • At least one solution above works (Balder's), and it's not doing serious byte code manipulation. – Tihamer Feb 09 '21 at 02:58