I've been working with JDK 1.5 annotations for several months now with exceptional results. Perhaps it's just a reflection of Tapestry, which has grown to use a pattern of design and development I've come to call composite coding.
Composite coding is a kind of intersection point between traditional class-oriented inheritance, aspect oriented programming, and code generation. This approach was already in place nearly two years ago, in Tapestry 3.0. When you define a class as abstract, and define transient and persistent properties (using XML) in Tapestry 3 what you are really doing is compositing your code with code generated by Tapestry.
This is not so much different from traditional approaches, where we composite the code we write with code from base classes often written by others. That's the nature of inheritance (and whether that is a good model for creating a framework is a discussion for another day).
Aspect oriented programming is also a way of compositing different sets of code together to form the final class definitions used at runtime. In fact, aspect oriented programming is such a general term that it easily encompasses what Tapestry is doing ... in fact, the uses of composite coding in Tapestry are very much within the domain of aspect oriented programming: cross cutting concerns related to page pooling and server-side state managements.
What I like about annotations, as applied to Tapestry 4, is that it integrates the code with the composite operations to be applied to that code ... that is, to make a property persistent, add the @Persist annotation directly to the method, rather than configure persistence elsewhere (in Tapestry 3, in a companion XML file).
One of the complaints of this approach is that it puts configuration data directly inside Java class files, where changing configuration requires a recompile and re-deploy of the application.
That's a pretty lame complaint and, to my ears, is an attempt to resist change. Sorry folks, get used to embracing change ... or get used to saying "want fries with that?"
Even in an extreme example, say the EJB3 @Table annotation, the problem isn't with the annotations ... it's with the framework that utilizes those annotations. In many cases, an annotation sets a default value ... one that may need to be overridden to match a particular deployment configuration.
In fact, this is only one step removed from the mess that was EJB 1 and 2 ... where the mythical application deployer role was responsible for configuring and deploying EJBs by configuring deployment details such as ... databases and tables. Sun dropped to ball on describing how this person did their job. In fact, the only effective way would be to explode EARs, including nested WARs and JARs, so that the ejb-jar.xml configuration files (and their many friends) could be edited, then repackage everything.
In my view, BEA, IBM and JBoss (in fact, the whole application server sector) dropped the ball by NOT providing a mechanism to override the defaults specified inside the ejb-jar.xml files short of exploding and repackaging.
Annotations are no different; tools and frameworks that rely on annotations should take into account the fact that deployment may need to change some of the configuration specified in the annotations. In other words, don't work directly from the code annotations, construct a model from the annotations, but allow other vectors for controlling that model before it is acted upon.
Yes, this is the model used in Tapestry 4, where an optional specification is parsed into memory, then modified to reflect information gleaned from annotations.
One of the major things to admire about the Ruby community is the emphasis on The Code Is The Thing. Nobody gives a sneeze at all the meta-programming in Ruby, because, in the end, they still have the standard ways to read and update Ruby object properties. JDK 1.5 Annotations are as close as Java currently gets to meta-programming and it brings the emphasis back to The Code, where it belongs.
In the past, I often was asked why Tapestry used so much XML. "Because it's convenient" was my response ... a convenient way to store some rich, hierarchical data. Well, annotations are even more convenient, and I think, more in the spirit of how we should be coding.