Annotation let-down: A response

In a recent thread on TheServerSide.com, Rick Hightower, a fellow NFJS speaker, commented on the JSR-175/annotations specification, and I felt a little obligated to respond, since this is a common critique/criticism:

Why don’t you like the implementation? I hate the fact that your code has to import the annotations and then your code is tied to the annotation. It does not seem that different than depending on a interface (i.e., a marker interface). I’d like to see a soft import for annotations that does not impact compilation. (from TheServerSide.com)
Rick, no part of JSR 175 was more hotly debated, or contested, than the requirement we made that annotations be present not only at compilation-time, but at run-time. I could go back and show you the weeks of emails that went flying back and forth between the EG members, trying VERY hard to come up with a solution to the problem, but none could really be created, given Java’s basic platform requirements, one of which was that the language was strongly-typed.

The basic problem was this: if the compiler runs across an annotation, and it doesn’t match an annotation type defined anywhere in the compilation classpath of imported symbols, what is the proper behavior? In any other scenario, such as:

public class App
{
  public static main(String[] args)
  {
    Systme.out.println(“Hello, world!”);
  }
}
the behavior is extremely clear: this is an error, and compilation needs to fail to signal as much. But the proposal for “soft imports” of annotations would lead to a much grayer–and potentially disastrous–scenario, where the compiler SHOULD flag an error during compilation, but doesn’t:
public class App extends Object
{
  @Overide public String toString(Object obj) { return “App”; }
}
Here, the human eye can clearly see that the class means to take advantage of the @Override compiler annotation to ensure that the toString() method is defined similarly in a base class, but because of a typo, the compiler now, under a soft-import rule, will simply ignore the annotation. This is the worst of all violations of the Principle of Least Surprise–the programmer believes the annotation is present, and that the override is acceptable, where in reality the annotation is ignored, the override isn’t checked, and the code will fail to operate as expected.

The big IDE vendors were particularly upset at this idea, leading one to claim, “If we cannot solve this problem we will consider JSR 175 to have been a failure.” Unfortunately, then, we failed–there is no good way to solve this problem without breaking the fundamental vision of the Java platform. We tried a variety of ideas, including a few centered around the JDK 1.4 assertion idea (some kind of runtime flag indicating which annotations were safe to ignore), but couldn’t work out the basic semantics of such without requiring a definition of the annotation to be present on the compilation path. And frankly, in the grander scheme of things, it makes sense to me that annotations ARE required at compilation time–just as interfaces, helper classes, member field types, and other types are required to be present at compilation time, as well.

Rod Johnson continued his critique of annotations by citing the following two reasons:

  1. No proper mechanism for overriding annotations at runtime, despite the fact that just about any framework that uses annotations is going to need to consider doing that.
  2. Inability for an annotation to extend an existing interface (even if that interface is simple enough to sit within annotations). Of course there are implementation issues around this one, I guess. But it means that it’s hard to avoid code duplication when working with annotations and alternative metadata sources–something that’s going to be particularly important until everyone and their dog uses Java 5, and anyway will remain important to work with existing code that may not have the right annotation.
Rod, the suggestion for overriding annotations at runtime was made, and we almost unanimously shot it down, because there are no facilities for changing any other of a type’s static type information at runtime: I cannot change methods, fields, inheritance, or interfaces at runtime, either. Such behavior belongs in the world of MOPs, perhaps, and hence your interest in such, but in a statically-typed world such behavior is not part of the landscape. Like it or hate it, such is the world that Java is a part of. (By the way, annotations are intended for much more than just frameworks–witness the annotations the javac compiler already recognizes, and other systems beyond frameworks are going to pick up on this in spades in the coming years. Just wait until the design-by-contract folks start talking to compiler folks again.) And you already answered your second criticism, that annotations extending existing interfaces would be difficult to implement. In truth, annotations and interfaces aren’t really the same thing, so expecting one to be able to inherit the other wasn’t something I’d consider good design. I didn’t even like the “@interface” keyword–I preferred something like “annotation” or “attribute” instead, but Josh (rightly) pointed out that introducing new keywords into a language ten years old was going to be a Bad Thing. (And yes, the same was true of “assert”, and they did it anyway, and look how well that turned out–they broke JUnit, of all things!)

Nutshell version of all this, the JSR 175 EG did, in fact, think long and hard about “soft imports” and “runtime annotation modification”, and both ideas were shot down for what we felt were good reasons.