; last updated - 5 minutes read

Once again Lukas Eder has written an article I mostly agree with - but not completely. Lukas claims Annotations have become an antipattern.

The nice thing about annotations is you can define your own annotation. It's easy, and it's extraordinary popular among framework designers. So we see annotations in the bean validation API, in JPA, in CDI, in JSF, in EJBs, WebServices and Rest and much more.

Annotations - a programming language of their own

While I absolutely love these annotations, there's a problem: they are starting to become a programming language of their own. What do you think about something like this?

@NotNull @Size(min=6,max=10) @Pattern("[a-zA-Z]+") @Inject @UserID @Column(name="userID") private String userID; @Valid @ManagedProperty("#{customer.account}") @OneToOne private Account account;

It's hard to spot the variables. They are drowning in a flood of annotations, each calling for attention. It In theory, annotations aren't part of the Java program. They are merely hints to the compiler or the editor. In this case, they are hints to the frameworks the programmer uses. The program won't work correctly without the annotations. In my eyes, that's a legal use case, even if it probably goes way beyond what annotations were originally meant to be.

Using defaults to avoid annotations

The sheer quantity of annotations quickly becomes a problem. A way to solve - or alleviate - the problem is to use clever defaults. Hibernate beans don't need an annotation for every field. It suffices to annotate fields that aren't mapped trivially. This pattern makes annotations the exception rather than the rule.

The slippery slope from configuration to programming

However, once programmers become accustomed to using annotations, they want to configure everything with exceptions. The Hibernate manual has an example how not to do it:

@Embedded @AttributeOverrides( { @AttributeOverride(name="city", column = @Column(name="fld_city") ), @AttributeOverride(name="nationality.iso2", column = @Column(name="nat_Iso2") ), @AttributeOverride(name="nationality.name", column = @Column(name="nat_CountryName") ) //nationality columns in homeAddress are overridden } ) Address homeAddress;

Like so often, the comment indicates there's something wrong. Annotations should be simple. Programmers should be able to grasp their meaning without having to read a comment.

Annotations have become such a popular replacement of XML configuration files because they a lot simpler than XML configuration files. But in this case, it's questionable that annotations are simpler. The only advantage of the annotation is that it's located near the variable it influences (instead of being moved to a separate XML file in a different folder). By the way, Lukas has an example of an annotation that's a lot worse.

Actually, it's a bit misleading to call this particular annotation a "configuration". It describes a number of mappings. Mappings, in turn, are functions. Functions are best expressed in code. So why don't we use the new Java 8 lambda expressions instead of clumsy annotations?

Functions to the rescue?

In fact, the entire O-R-Mapping could be expressed as a function. It's even in the name: it's a mapping. Expressing such a mapping as a function has many advantages. For one, we could debug what used to be our configuration file. Second, it's blazing fast byte code instead of annotations that are interpreted at runtime. Maybe we could even get rid of reflection, giving our application another performance boost. Plus, using real types (instead of strings indicating the name of the variable) makes the configuration type-safe. Over the years, many annotations have begun to form a type system that's independent of Java's type system. Problem is, neither the compiler nor the IDE is aware of this type system, so they can't help us detect errors.

So it seems it was wrong to consider the O-R-mapping simple "configuration". It should be actual code.

The same can be said about the JSR303 bean validation annotations. They can easily be expressed as functions. Lambdas are more flexible than annotations, making it easier to implement custom validators. Plus, they can be debugged.

Musing about the syntax

While I write this I wonder what the syntax should look like. I don't want to return to the bad old days when the configuration was separated from the code, so the syntax shouldn't be too different from the annotation syntax. For the moment, let's simply think of annotations that are debuggable functions which register to certain system events, such as validating or persisting an object.

Wrapping it up

So that's where I agree with Lukas: configurations tend to become real programs, but we aren't allowed to call them programs. That's wrong. It would be better to replace annotations by real functions.

However, I do not think that annotations are an invalid tool for the basic use cases. Annotations like @Size or @NotNull are very useful, I wouldn't want to miss them. Plus, there are structural annotations such as @WebService, @ManagedBean and @Inject. These annotations don't define mappings, so it's hard to see why they have to be replaced by functions. On the other hand, converting these annotations into debuggable system event listeners wouldn't hurt, either.

Until someone implements debuggable annotations (or another solution, for that matter), framework developers should be careful when creating annotations. It's the same old story: what starts as a useful tool is overused and misused a short time later.


Dig deeper

How JPA 2.1 has become the new EJB 2.0 by Lukas Eder


Comments