Modify a class definition's annotation string parameter at runtime

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.


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();
}

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);

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 {
    //...
}