When Java 1.5 introduced Annotations, Enterprise Developers had high hopes that it would make their life developing EJBs and other Enterprise Artifacts much easier. See for example this contemporary article: Simplify enterprise Java development with EJB 3.0.

Since then however, using Annotations the way Java Enterprise started to use them had some unforeseen consequences and side effects, which still go almost unnoticed today. Fortunately not completely unnoticed, see for example this completely valid comment on StackOverflow titled “Why Java Annotations?“, this article with very good points: “Are Annotations Bad?“, and others: “Magics Is Evil“, “Annotations…Good, Bad or Worse?“.

Not All Annotations Are Created Equal

Although the above discussions do address many valid points, not all Annotations are the same.

There are two categories of Annotations depending on whether they have any influence on the runtime behavior of the program. First, there are the harmless types, which have no impact on running code at all, and there are the evil ones, which modify runtime behavior. Examples for harmless Annotations include: @Deprecated, @Override, @SuppressWarnings, and so on. Evil Annotations include: @Entity, @Table, @PostConstruct, @ApplicationScoped, etc.

Additionally there is a subset of the harmless Annotations, which are positively useful. These are the ones that offer some kind of additional (statically checked) functionality compile-time to catch some error or safety issue. Useful Annotations are for example: @Override, @NonNull/@Nullable (from the Checker Framework), etc.

Why Are Evil Annotations Bad?

Aside from having defined a subset of Annotations evil, why would one want to avoid using them?

Just image if the standard Java Date class would have a @PostConstruct method. This Annotation denotes that the said method should be invoked right after the construction of the object is done. This functionality is not handled by the JVM itself, so right off the bat the Date class implicitly pulls some yet unknown framework or container that actually has nothing to do with the Date itself semantically. What if the consuming code does not run in any Container, just the plain JVM? This Annotation effectively reduces the reusability of the class significantly. Additionally it would be a nightmare to unit-test anything using Date, because now you have to make sure the post-construction is triggered somehow each time, simulating a compatible container. This might seem ridiculous to consider, a Date class needing a Container to run, but this is exactly what evil Annotations force on classes, methods and parameters.

Admittedly, business logic is often more complex, has more dependencies and relations than a relatively simple Date class. However, that does not in any way excuse unnecessary explicit or implicit dependencies or constraints within a class, and evil Annotations are just that: dependencies and constraints.

The Enterprise Trap

Unfortunately evil Annotations were largely legalized by Java Enterprise 5. Trying to correct the serious usability issues of the previous Enterprise APIs, Annotations were used to hide all the difficult and redundant parts of the System. The new JEE 5 was praised for being "lightweight" and "simple", which on the outside seemed true, but was (and still is) nonetheless a slight but crucial misrepresentation of what is really going on.

@Stateless public class DocumentRepository { public Document getDocument(String title) { ... } ... }

So, to get a Stateless EJB, one has to “only” annotate a class with a @Stateless Annotation. While it is true that the actual action of writing this class is simple enough, please notice that with this evil Annotation this class is now bound to a couple of hundred pages of specification which can only be implemented with a couple of hundred megabytes of software called the Application Server. It is not, in any meaningful sense of the word “lightweight”. So this Annotation became a placeholder for actual Java Code that previously needed to be written, but instead of simplifying the framework and specification itself, still needed to be there in some form. So it was hidden under an Annotation.

Unfortunately, this workaround became a pattern, and now evil Annotations are legally everywhere: JPA, CDI, Common Annotations, JAXB, etc.

Evil Annotations Are Sometimes in the Wrong Place

Precisely because they are mostly workarounds, sometimes evil Annotations are regarded as exempt from basic programming best-practices such as Single Responsibility Principle or Separation of Concerns.

Let’s consider the following example from CDI:

@ApplicationScoped public class DocumentFormatter { ... }

The above Annotation describes that this class should be a CDI Bean, that is, its instantiation should be exclusively handled by CDI and additionally there should be only one instance per application.

This information does not belong in this class. The functionality of this service (whatever that may be) has nothing to do with how it is used in the current application. These are two very distinct concerns.

A similar example from JPA:

@Entity @Table("PERSON") public class Person { ... }

The problem is that these kinds of objects are usually "domain objects", which couples the persistence directly to domain objects. Or worse, Data Transfer Objects are used to shuffle data to and from other objects, which makes the whole construct fragile because of the tight coupling between objects. Either way, this is a wrong approach.

All of these additional functionalities and/or information should be external to these classes, but they get a pass because they are “only” Annotations.

Evil Annotations Are Sometimes Contagious

Annotations sometimes infect other objects. Consider the CDI Bean above. Every other object which uses this one, and every dependency this Bean uses must be annotated now with one of the CDI Annotations, otherwise the dependency tree can not be constructed.

The @Entity Annotation does the same. Every other object now that needs to be persisted needs to be annotated and because there are relationships between these objects, sooner or later all persisted objects will have this Annotation. There is no room for using third party objects natively (other than serializing or wrapping them), no room to use other persistence mechanisms (like pushing some objects to a NoSQL DB).

These Annotations are effectively making these objects non-reusable. They are only usable in a very strict, controlled, opaque environment, that can not be integrated with anything else.

What Are the Alternatives?

Is it XML? No it definitely isn’t, at least for the examples above.

The Spring Framework for example contends (or at least used to) that plugging objects together is configuration and therefore can be outsourced to XML configuration files. However, does a certain dependency really need to be changed runtime or without recompiling? If it does not, it’s hard to argue that it should be externalized into something that is effectively a whole other language, that can not be refactored easily, can not be tested easily and can not be managed without specific tools.

The real alternative is of course good old Java Code, properly encapsulated and separated. Yes, code that plugs objects together, although sometimes regarded as boilerplate, is not bad. It makes the code readable, debuggable, refactor-able, which is good! Only long, complicated, redundant boilerplate is bad, for example “Read all about EJB 2.0“. But the solution is not to get rid of all boilerplate or to hide the boilerplate into another language, rather than to have a clear and simple architecture that requires no more information than it needs in a straightforward, preferably object-oriented and easy way.

The same applies of course to JPA, Spring and others too. Instead of misusing Annotations to express functionality that may result in classes and methods like in this Stackoverflow question: “Arguments Against Annotations“, why not use the tools that are already given: the Java Language and its Compiler, to solve these problems the “old fashioned” way, using object-orientation and software design best-practices?

Summary

An Annotation is evil, if it represents some additional functionality or constraint that alters runtime behavior of code. This is bad, because it hides aspects of a class or method and therefore makes the Code harder to understand, reuse, debug, refactor and to test.

Unfortunately Java Enterprise largely desensitized the Java Developer Community against such misuses of Annotations and so there is little hope that subsequent specifications of Enterprise Java or other “official” frameworks will address these issues.

What can be done however, is to be on the lookout for evil Annotations, avoid them if possible, and to write new frameworks and software which replace those that won’t recognize these problems.