Evil Annotations

When Java 1.5 introduced Annotations, Enteevil_annotationrprise 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. Although a lot of businesses did invest in Java Enterprise on both the provider and user sides, which made it on a purely market share point of view a success, up until JEE 5 developers largely tried to avoid it in favor of Spring, which was much more lightweight and (compared to the alternative) a breeze to use.

Let us also remember however, that the goal of Java Enterprise was to have “reusable business components” and in this sense it failed completely. Even today, with the newest release, no one actually downloads a “ShoppingCart” EJB from the Maven Repository, which is then “Assembled”, “Configured” and “Deployed” by the various Enterprise Developer Roles.

So with this goal basically abandoned, the JEE 5 release concentrated on winning back the developers by making EJBs much more approachable and usable, this is where Annotations came in. This actually moderately succeeded, thereby legalizing much of the questionable architecture decisions (see below).

The biggest selling point was, that the new release is lightweight and simple, which on the outside seems true, but 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 {
   ...
}

This in itself is not problematic, but problems arise when this Person is reused as a domain model object. If this is a domain object, than it should not be responsible for persistence too. If this is not a domain object, but purely persistence object, then usually its data has to be shuffled into a very similar object, sometimes called a Data Transfer Object, either through copy-paste or through reflection-based libraries. 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

evil_injection

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 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.

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s