66

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?

4
  • 1
    Class has an annotations and a declaredAnnotations map fields that you can try to modify with reflection...
    – assylias
    Commented Jan 10, 2013 at 23:20
  • 4
    grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… 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
    Commented Jan 10, 2013 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? Commented Jan 11, 2013 at 0: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
    Commented Sep 7, 2018 at 11:19

7 Answers 7

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.

15
  • 1
    Thanks Balder, Can you please explain in line f = handler.getClass().getDeclaredField("memberValues"); what is memberValues here? Commented Apr 5, 2015 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
    Commented Apr 13, 2015 at 5:30
  • Can somebody tell me how to change annotation on field? This solution works filne for class annotations but not for the fields :( Commented May 18, 2015 at 10:05
  • 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
    Commented Sep 29, 2016 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
    Commented Sep 30, 2016 at 14:52
45

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();
}
7
  • 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
    Commented Dec 3, 2013 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
    Commented Dec 3, 2013 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
    Commented Dec 3, 2013 at 23:58
  • 23
    this does not work with java 8. getDeclaredField("annotations") seems not to work anymore Commented Jul 3, 2014 at 13:44
  • 5
    This article shows how to manipulate the annotations in Java 8: rationaleemotions.wordpress.com/2016/05/27/…. Look for "public class AnnotationHelper" at about the middle of the page.
    – mvermand
    Commented Jan 5, 2017 at 7: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);
1
  • Where is matchedAnnotation used?
    – Zon
    Commented Jun 17, 2021 at 21:40
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 {
    //...
}
4
  • Adding beans name in @Componenet("...") and @Scope for SCOPE_PROTOTYPE is also needed.
    – a.k
    Commented Mar 3, 2019 at 8:45
  • 5
    Is there a complete example for this? This is literally what I need in my current use case. But I can't seem to get it to work. Commented May 22, 2019 at 11:50
  • And also can this be used with JPA annotations? Or just spring annotations? Commented May 22, 2019 at 12:31
  • What if the property type was boolean?
    – Sajad
    Commented Jun 6, 2023 at 11:25
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();
}
1
  • 2
    java.lang.UnsupportedOperationException at java.util.AbstractMap.put(Unknown Source) Commented Apr 28, 2017 at 9:55
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?

2
  • 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? Commented Jan 10, 2013 at 23:18
  • At least one solution above works (Balder's), and it's not doing serious byte code manipulation.
    – Tihamer
    Commented Feb 9, 2021 at 2:58

Not the answer you're looking for? Browse other questions tagged or ask your own question.