Java Annotations Are a Big Mistake
Annotations were introduced in Java 5, and we all got excited. Such a great instrument to make code shorter! No more Hibernate/Spring XML configuration files! Just annotations, right there in the code where we need them. No more marker interfaces, just a runtime-retained reflection-discoverable annotation! I was excited too. Moreover, I’ve made a few open source libraries which use annotations heavily. Take jcabi-aspects, for example. However, I’m not excited any more. Moreover, I believe that annotations are a big mistake in Java design.
Long story short, there is one big problem with annotations—they encourage us to implement object functionality outside of an object, which is against the very principle of encapsulation. The object is not solid any more, since its behavior is not defined entirely by its own methods—some of its functionality stays elsewhere. Why is it bad? Let’s see in a few examples.
@Inject
Say we annotate a property with @Inject
:
import javax.inject.Inject; public class Books { @Inject private final DB db; // some methods here, which use this.db }
Then we have an injector that knows what to inject:
Injector injector = Guice.createInjector( new AbstractModule() { @Override public void configure() { this.bind(DB.class).toInstance( new Postgres("jdbc:postgresql:5740/main") ); } } );
Now we’re making an instance of class Books
via the container:
Books books = injector.getInstance(Books.class);
The class Books
has no idea how and who will inject an instance of class DB
into it. This will happen behind the scenes and outside of its control. The injection will do it. It may look convenient, but this attitude causes a lot of damage to the entire code base. The control is lost (not inverted, but lost!). The object is not in charge any more. It can’t be responsible for what’s happening to it.
Instead, here is how this should be done:
class Books { private final DB db; Books(final DB base) { this.db = base; } // some methods here, which use this.db }
This article explains why Dependency Injection containers are a wrong idea in the first place: Dependency Injection Containers are Code Polluters. Annotations basically provoke us to make the containers and use them. We move functionality outside of our objects and put it into containers, or somewhere else. That’s because we don’t want to duplicate the same code over and over again, right? That’s correct, duplication is bad, but tearing an object apart is even worse. Way worse. The same is true about ORM (JPA/Hibernate), where annotations are being actively used. Check this post, it explains what is wrong about ORM: ORM Is an Offensive Anti-Pattern. Annotations by themselves are not the key motivator, but they help us and encourage us by tearing objects apart and keeping parts in different places. They are containers, sessions, managers, controllers, etc.
@XmlElement
This is how JAXB works, when you want to convert your POJO to XML. First, you attach the @XmlElement
annotation to the getter:
import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Book { private final String title; public Book(final String title) { this.title = title; } @XmlElement public String getTitle() { return this.title; } }
Then, you create a marshaller and ask it to convert an instance of class Book
into XML:
final Book book = new Book("0132350882", "Clean Code"); final JAXBContext ctx = JAXBContext.newInstance(Book.class); final Marshaller marshaller = ctx.createMarshaller(); marshaller.marshal(book, System.out);
Who is creating the XML? Not the book
. Someone else, outside of the class Book
. This is very wrong. Instead, this is how this should have been done. First, the class that has no idea about XML:
class DefaultBook implements Book { private final String title; DefaultBook(final String title) { this.title = title; } @Override public String getTitle() { return this.title; } }
Then, the decorator that prints it to the XML:
class XmlBook implements Book{ private final Book origin; XmlBook(final Book book) { this.origin = book; } @Override public String getTitle() { return this.origin.getTitle(); } public String toXML() { return String.format( "<book><title>%s</title></book>", this.getTitle() ); } }
Now, in order to print the book in XML we do the following:
String xml = new XmlBook( new DefaultBook("Elegant Objects") ).toXML();
The XML printing functionality is inside XmlBook
. If you don’t like the decorator idea, you can move the toXML()
method to the DefaultBook
class. It’s not important. What is important is that the functionality always stays where it belongs—inside the object. Only the object knows how to print itself to the XML. Nobody else!
@RetryOnFailure
Here is an example (from my own library):
import com.jcabi.aspects.RetryOnFailure; class Foo { @RetryOnFailure public String load(URL url) { return url.openConnection().getContent(); } }
After compilation, we run a so called AOP weaver that technically turns our code into something like this:
class Foo { public String load(URL url) { while (true) { try { return _Foo.load(url); } catch (Exception ex) { // ignore it } } } class _Foo { public String load(URL url) { return url.openConnection().getContent(); } } }
I simplified the actual algorithm of retrying a method call on failure, but I’m sure you get the idea. AspectJ, the AOP engine, uses @RetryOnFailure
annotation as a signal, informing us that the class has to be wrapped into another one. This is happening behind the scenes. We don’t see that supplementary class, which implements the retrying algorithm. But the bytecode produced by the AspectJ weaver contains a modified version of class Foo
.
That is exactly what is wrong with this approach—we don’t see and don’t control the instantiation of that supplementary object. Object composition, which is the most important process in object design, is hidden somewhere behind the scenes. You may say that we don’t need to see it since it’s supplementary. I disagree. We must see how our objects are composed. We may not care about how they work, but we must see the entire composition process.
A much better design would look like this (instead of annotations):
Foo foo = new FooThatRetries(new Foo());
And then, the implementation of FooThatRetries
:
class FooThatRetries implements Foo { private final Foo origin; FooThatRetries(Foo foo) { this.origin = foo; } public String load(URL url) { return new Retry().eval( new Retry.Algorithm<String>() { @Override public String eval() { return FooThatRetries.this.load(url); } } ); } }
And now, the implementation of Retry
:
class Retry { public <T> T eval(Retry.Algorithm<T> algo) { while (true) { try { return algo.eval(); } catch (Exception ex) { // ignore it } } } interface Algorithm<T> { T eval(); } }
Is the code longer? Yes. Is it cleaner? A lot more. I regret that I didn’t understand it two years ago, when I started to work with jcabi-aspects.
The bottom line is that annotations are bad. Don’t use them. What should be used instead? Object composition.
What could be worse than annotations? Configurations. For example, XML configurations. Spring XML configuration mechanisms is a perfect example of terrible design. I’ve said it many times before. Let me repeat it again—Spring Framework is one of the worst software products in the Java world. If you can stay away from it, you will do yourself a big favor.
There should not be any “configurations” in OOP. We can’t configure our objects if they are real objects. We can only instantiate them. And the best method of instantiation is operator new
. This operator is the key instrument for an OOP developer. Taking it away from us and giving us “configuration mechanisms” is an unforgivable crime.
- Java Annotations Are a Big Mistake (webinar #14); 4 May 2016; 744 views; 13 likes
- Dependency Injection Container is a Bad Idea (webinar #9); 1 December 2015; 1264 views; 19 likes
- Why Getters-and-Setters Is An Anti-Pattern? (webinar #4); 1 July 2015; 3095 views; 53 likes
Reference: | Java Annotations Are a Big Mistake from our JCG partner Yegor Bugayenko at the About Programming blog. |
It’s a shame to see an article in 2016 that tries to throw Java back in to Middle Ages.
I really like this website and overall the articles are very good.
We are living into an era of automation and fast speed. Who do you think will implement this in an enterprise application?
I cannot believe someone is putting a lot of effort in this.
I didn’t decide is it a joke to get traffic or a lack of education :)
I think there is a mistake in the reasoning in this article.
You cannot add behavior to the object via annotation. You cannot add a new method or change an existing one or add/remove some object property using annotation. So saying “they encourage us to implement object functionality outside of an object” is incorrect, because annotation is not a “functionality”, but it’s a configuration for an existing object behavior. It’s more like a configuration file of your application, but located inside the source code itself.
I couldn’t disagree more with everything in this article, and can’t imagine developing a real world application without annotations.
“Spring Framework is one of the worst software products in the Java world. If you can stay away from it, you will do yourself a big favor”
To other people reading this: do yourself a big favor and ignore everything in this article.
I agree with you Luis Fernando Planella
Totally agree with you!!! This sounds like some angry customer complaining…
When I started reading this article I wanted to comment every single sentence and argue why it so wrong. But when I reached the end it became clear that the author seems to be an OO enthusiast. And from this point of view he’s maybe (!) right. I think intentionally Java started as a language which was highly object oriented. But after the years it started supporting a lot of other programming paradigms, too. With annotations Java could be used in an aspect oriented way. Now with Java 8 even functional programming is possible. And of course this is not… Read more »
OOP is just a tool darling, just as AOP is, don’t get fooled there is no silver bullet.
And let’s be clear nobody gives a crap about architectural paradigms, frameworks… What matters about software is the value it brings to users. (and as a software engineer I love clean architecture and great designed frameworks).
It is a series, isn’t it?
in the previous episodes:
DI Containers are Code Polluters
https://www.javacodegeeks.com/2014/10/di-containers-are-code-polluters.html
Getters/Setters. Evil. Period.
https://www.javacodegeeks.com/2014/09/getterssetters-evil-period.html
…
The next article in this series is possibly “Factory Pattern is evil”.
The article started with an idea that it can show some incorrect usages of annotations. But then it continued on a topics that have nothing to do with it. Object creation and configuration, incorrect role of responsibilities and etc… It cannot be more false to say : “Only the object knows how to print itself to the XML. Nobody else!”. I am sorry to ask but how many responsibilities do your objects have. I started counting and at this point I am already exhausted. I really wouldn’t want to look at the code that is produced. Another totally false “statement”… Read more »