Is your feature request related to a problem? Please describe.

In 3.0, AnnotatedMember does not have a non-deprecated way to search for meta-annotations. For example, let's say there's a custom ValueSerializerModifier activated by an annotation called Foo, and this Foo annotation is supposed to be usable as a meta-annotation. In other words, if annotation Bar is annotated with Foo, then the custom code should be able to find the Foo annotation (on Bar) even if the member us annotated only with Bar.

Describe the solution you'd like

Removing the deprecation notice from AnnotatedMember.getAllAnnotations() would be one way to resolve the issue. This would allow custom code to search for meta-annotations by inspecting all annotations on the member.

Usage example

No response

Additional context

No response

Comment From: JooHyukKim

Sounds reasonable API to have. But I wonder why the deprecation happened in the first place 🤔 (it might say on the codebase, but dont have laptop atm)

Comment From: cowtowncoder

Quick note: I think we talk about just annotations, not meta-annotations; changed description appropriately.

But I am not sure why access via getAllAnnotations() is needed, over calling getAnnotation() for specific annotations(s) desired? Convenience?

Comment From: dnault

But I am not sure why access via getAllAnnotations() is needed, over calling getAnnotation() for specific annotations(s) desired? Convenience?

It's for cases when the target annotation isn't directly on the annotated member, and the module author does not know which annotations will appear directly on the annotated member; the member may be annotated with an annotation the module author has never heard of, and that annotation is itself annotated with the annotation the module knows about.

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface AnnotationKnownByModule { ... }
@AnnotationKnownByModule  // <- used as meta-annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface CustomUserAnnotation { ... }
public class MyPojo {
    @CustomUserAnnotation
    public final String name;
    ...
}

It's my understanding that getAnnotation(AnnotationKnownByModule.class) will not find the annotation on MyPojo.name.

Imagine a Jackson module wants to know MyPojo.name is indirectly annotated with AnnotationKnownByModule. In order to do this, it needs to get all the annotations (even the ones it knows nothing about, like CustomUserAnnotation) so it can look for the meta-annotation.


Maybe an alternative to un-deprecating getAllAnnotations() would be to add a new method that handles scanning for meta-annotations.

Comment From: cowtowncoder

getAllAnnotations() provides a set of annotations that getAnnotation() can access, there is no difference. So if it's contained in Map of getAllAnnotations(), can access via getAnnotation(), if not it cannot.

As to meta-annotation support... I don't think I would want to add support for yet more traversal.

Comment From: dnault

Sorry, let me see if I can do a better job of explaining.

I understand that any annotation in getAllAnnotations() can be accessed by getAnnotation() -- but only if you know which annotation to ask for.

As a concrete example of why you might not know which annotation to ask for, I'm working on porting a Jackson 2 module that supports encrypting field values. The user applies an @Encrypted annotation to the field they want encrypted/decrypted during serialization/deserialization.

At this point you might be thinking the module can just call getAnnotation(Encrypted.class), but there's a complication.

This annotation has an attribute that specifies the encryption key to use. Rather than requiring the end user to specify the encryption key every time the annotation is applied, the module lets the user define their own annotation that doesn't require the user to specify the key. To do this, the module supports @Encrypted being used as a meta-annotation; in other words, it can be applied to other annotations so the other annotation "inherits" the properties of the @Encrypted annotation. (This "inheritance" is not a language feature, but rather a convention that requires using reflection to find the meta-annotation.)

Pseudocode for the simplified version of this custom annotation might look like this:

@Encrypted(key = "super-secret-key")
public @interface SuperSecret

This lets the user annotate their POJOs with their custom @SuperSecret annotation and not have to worry about specifying the key name each time.

Now let's think about how the Jackson module finds the @Encrypted annotation. If it calls getAnnotation(Encrypted.class) it won't find it, because this annotation is not present on the field. Instead it needs to somehow get the @SuperSecret annotation so it can inspect it and find the @Encrypted annotation on @SuperSecret.

So the module just needs to call getAnnotation(SuperSecret.class), right? Unfortunately it can't, because the module doesn't know about the SuperSecret annotation -- it's a custom annotation created by the user of the module.

What the module can do it ask for all annotations, and scan them to see if any annotations are themselves annotated with @Encrypted.

Comment From: cowtowncoder

Ah ok. I figured out something like this would make sense.

I wish I had added proper comment on @Deprecated so I would remember exact reasoning. Possibly it was to avoid exposing Map (although, AnnotationMap is immutable so it's not safety concern), or maybe to make it possible to lazily collect annotations.

But given as things are, I think question would be which iteration mechanism should be supported -- I think iterating over entries is probably most sensible? (as opposed to, say, Stream of Class<?> keys only). Open to specifics of method to add to replace deprecated variant.

Comment From: cowtowncoder

@dnault I noticed AnnotationMap itself exposes:

public Iterable<Annotation> annotations();

so with Stream we'd probably want to expose

public Stream<Annotation> annotations();

in AnnotatedMember -- would this work as replacement?

Comment From: dnault

@cowtowncoder Yes, that would work. Thank you.

Comment From: cowtowncoder

Thinking of adding new method in 2.20 to help migration.

In 3.0 can change signature of AnnotationMap.annotations() too (but not in 2.x)

EDIT: alas, existence (and deprecation of) badly typed Annotated.annotations() is problematic wrt 2.20 changes. So may only change in 3.0 after all.