Annotations + Inheritance = Confusion

21072007

When annotations were introduced into Java, their interaction with inheritance was kept very simple. Annotation types can’t extend/subclass each other, and the presence or absence of an “@Inherited” meta-annotation indicates whether or not the annotation is inherited by subclasses. Anything else is unspecified (such as how annotations on fields that are “hidden” should be treated).

Unfortunately, if JSR 250 “Common Annotations” is anything to go by, this seems to have proved rather too simplistic. The result is that these annotations have instead ended up specifying their own individual, often somewhat ad-hoc rules for how they interact with inheritance, hiding and overridding of fields and methods (or in some cases, leaving this behaviour entirely unspecified).

So on top of Java’s well-defined semantics for inheritance, overriding, and hiding, we now have annotations that use natural-language descriptions to specify their own rather different semantics.

Given the variety of situations that need to be covered, and the inevitable variation in quality, such rules can easily be incomplete, ambiguous, confusing, and even contradictory. Conversely, we also have annotations that do not adequately specify how inheritance affects them.

Pretty much any annotation that can be applied to fields or methods is going to have to either address this in some manner, or leave lots of situations inadequately specified. As and when we get JSR 308 “Annotations on Java Types” there will be even more cases to consider (for example, annotations on method arguments).

The problems that arise from this include:

When designing a new annotation, you have to sort out for yourself how it should interact with all possible inheritance, hiding and overriding scenarios for each element type that can use the annotation, and you have to document this adequately.

If the annotation needs processing at runtime, this can include having to accurately identify which fields and methods are and aren’t inherited, hidden, and overridden, whilst taking into account their modifiers, which combinations are illegal anyway etc. It ought to be far easier than this when you just want to find and process some particular annotation.

Even just making use of such annotations has plenty of potential banana-skins. If there’s any subclassing involved, you can’t rely on your understanding of the normal Java rules, or assume whatever you would consider to be reasonable, or learn one set of rules that apply to all annotations. Instead you need to understand each particular annotation’s individual rules and their interaction with the Java semantics.

This must surely limit the ability of IDEs and other tools to analyze the code (e.g. to show a method’s inherited annotations, warn about hidden/overridden annotations etc).

The “Common Annotations” specification does acknowledge this issue and attempts to at least partially address it by giving a set of general guidelines for inheritance of annotations.

However, it isn’t entirely clear whether these guidelines are limited to the “common annotations” themselves or are intended to also govern other sets of annotations. In any case, they are only guidelines, and individual annotations remain free to deviate from them. In fact, despite a statement that “all annotations defined in this specification follow the guidelines defined above unless explicitly stated otherwise”, even the common annotations themselves seem to deviate from these guidelines more than they keep to them – one would be hard-pressed to discern any of the guidelines from looking at the annotations.

What’s more, where an individual annotation makes statements that differ from the guidelines, it is unclear whether the rest of the guidelines still apply, or exactly how the two sets of rules should be combined. For example, the guidelines define a certain treatment of inheritance without mentioning the “@Inherited” meta-annotation, and some of the annotations don’t have “@Inherited” but do have their own explicit inheritance rules. So if an annotation doesn’t include “@Inherited” and it’s description doesn’t describe inheritance, should it be taken as not being inherited or should it be taken as inherited as per the guidelines? What if it also deviates from some other aspect of the guidelines – should it still be inherited as per the guidelines or should it then be taken as not following the guidelines at all? How can you ever be sure?

As a prime example of all this, take the javax.annotation.Resource annotation:

It isn’t marked as “@Inherited” even though superclasses must be examined, because it instead specifies its own rules for how it is inherited.

It doesn’t explicitly state that it doesn’t adhere to the guidelines, but it contradicts most of them.

At least one of the guidelines (class-level annotations as a short-hand for member-level annotations) is not mentioned or explicitly contradicted, but would make no sense for this particular annotation.

Its description talks about “all” uses of the annotation, but does not specify whether the annotation can or cannot be used on static fields, final fields, static methods etc.

Its description says that the annotation should be processed when present on superclass “private fields and methods” even where a subclass overrides the method. However, this does not say how it is affected by an “@Resource” annotation on the overridding method – especially if its details differ from those of the overridden method’s annotation. The wording implies that both annotations must be processed, but invoking an overridden implementation of a method seems highly inappropriate and “unsafe” even if it can somehow be done. It’s also quite pointless if the overriding “setter” implementation is then also called anyway. At best, this seems highly unexpected behaviour compared to the normal Java rules. Alternatively, if the guidelines or normal rules for method overriding apply, then it is unclear what the documentation is trying to specify.

There is no mention of how the annotation should be treated for “hidden” fields and methods.

The only code that is provided is the annotation declaration itself. Although the annotation describes non-trivial rules for which fields and methods need to be processed, no reference implementation is provided for actually applying those rules (e.g. finding the relevant fields and methods of an object, or determining whether a given field or method does or does not have a relevant annotation to be processed). If you look around, you’ll find various application servers and maybe other products that include code for processing JSR 250 annotations (usually with lots of other baggage for their own particular environment). In particular there is a joint Interface21 and BEA Pitchfork project that provides this as a Spring “add on”. But why do we need more than a single, standard implementation if there is only one set of rules for the annotation?

Now maybe I’m being too pedantic (as usual!), or have misunderstood some of the rules, or have missed how they all fit together. But that’s really the point – I’ve looked at this over and over again, and I’m still struggling to be sure of its complete and precise meaning for all situations.

Similarly, one can argue that the documentation for this particular annotation just needs some clarifications. But if each annotation has its own rules and needs to cover all inheritance/hiding/overriding situations, then it’s inevitable that many of them will be inadequately specified.

And that’s just one annotation, from one specification. What happens once JSR 305 and JSR 308 are in place and people start inventing more and more annotations and using them in combination? Just how many different variations on “inheritance” can we cope with?

So, what can we do about this?

Ideally, I think I’d like to see:

A more comprehensive set of meta-annotations to more precisely specify an annotation’s valid targets and its interaction with inheritance, hiding and overriding. The aim would be take at least the bulk of this out of natural-language descriptions and guesswork, and turn it into a defined set of choices with precise meanings and values – whilst also allowing making it programmatically accessible and inspectible by tools. It ought to be possible to come up with a relatively small set of meta-annotations that cover most of the main options. There will always be some requirements beyond this, but it would at least get the bulk of stuff out of the way.

Reflection facilities for querying objects, fields and methods for an annotation taking into account the enhanced meta-annotations, with the aim of making the runtime finding and processing of an object’s annotations much simpler and more standardized. Maybe this could also include a mechanism for individual annotations to impose any additional non-standard rules that they require.

Oh well, there’s yet another nice little project to add to my “if I ever have time and funding” list…

In the meantime, here are my personal suggestions for anyone developing annotations under the present regime:

When creating an annotation, try to limit it to either not being inherited or being inherited exactly as defined by the @Inherit meta-annotation, if this is at all possible – even if that seems less than ideal for that particular annotation.

Failing that, try to keep to the guidelines given by JSR 250 Common Annotations, and explicitly reference them.

If you really, really need to have different semantics, I’d urge you to be really, really careful to provide a precise description that spells out exactly how the annotation should be interpreted for all possible inheritence, hiding and overriding situations.

If there’s any likelihood that anybody else will ever need to find and process the annotation, any rules for how the annotation is inherited should be backed up by actual code that implements those rules. For example, code to take a class or class member and return the instance of the annotation that should be processed for it (if any), or to find all members of a given object for which the annotation is present and should be processed.

Does anyone else have any good or bad experiences with these issues, or any other ideas for how this could be tackled?