Java Annotations – a conceptual overview

I’ve been a casual user of annotations in code for many years but had never bothered to understand how exactly they work, or why they even exist. I would pop in the requisite Hibernate, Spring or Lombok annotations where necessary and get on with it.

If the answer to the question “Why do annotations exist?” isn’t already clear to you, then it’s useful to start with questions like: Are annotations fundamental to writing code? If so, why don’t we introduce them to learners around the same time we introduce for-loops and function calls? If not, where exactly do they fit in in the scheme of things? And how come annotations rarely find a mention when you’re reading about OO, FP or even type systems?

My current understanding – that reconciles all these above questions – is that annotations (let’s say in Java) are not fundamental to writing code. They’re a shortcut and a kludge. They’re a shortcut when used to generate code during the build – as is the case with Lombok’s @Getter/@Setter/@Builder etc. They’re a kludge when Spring/Hibernate inspect them during run time in order to implement logic, create objects (or even classes!).

Then what makes annotations useful and even necessary sometimes? It is an acknowledgement that in commercial programming we need code generation shortcuts for common patterns, and even some amount of Spring / Hibernate magic is OK if the code itself looks more legible as a consequence. Languages like Scala/Lisp/Rust have a macro system that can provide the code generation benefits of annotations.

Limitations of Annotations

While annotations can layer on logic to existing code, their own semantics are loosely defined and hence a profusion of them could lead to more problems than they solve. In what order do multiple annotations on the same item run? What if they negate the effects of each other? Etc.

There’s a great article titled The case against annotations by Adam Warski which explores these questions in detail. He has an enlightening example for how Spring annotations for a HTTP Endpoint can be replaced with equivalent regular Java code. I’m pasting two code snippets from there below. Figure 1 shows the annotation based HTTP Endpoint and Figure 2 its Java equivalent.

Figure 1: Annotation based HTTP Endpoint
Figure 2: Java equivalent for annotations of Figure 1

If a piece of annotation baffles you it’s useful to re-arrange it like this mentally. Note that nothing about the usage of the annotations here indicates whether they’re processed at compile time (usually code gen) or processed at run time (using Java Reflection).

Relation of annotations to type systems

The other notable thing about Figure 2 is a reference to the function “hello::helloWorld” on the last line. Only Java 8 and newer versions have the function reference syntax. The equivalent code would be much more verbose in older versions.

Which brings me to an obvious observation about annotations. What’s their connection to the type system? And since they’re usually specified on other types (a function, class or instance variable), why not use the type system itself? Or are we going to hit a fundamental limitation with this approach?

Turns out annotations can be expressed within the type system (not sure if that’s correct in a fully general sense). But in a language like Java it might get clunky, and more importantly, not possible to provide an elegant interface to the annotation-based functionality through types alone.

Sticking to basic uses of annotations, I can see two approaches to implementing them via types. Consider a class SomeClass with one field that needs to be persisted into a database:

@Entity
@Table(name = "some_table")
class SomeClass {

     @Column(name = "column1")
     Int field1;
}

A literal translation on the lines of the HTTP Endpoint example above could look like:

class DbSomeClass {
     DbInt field1;

     public void saveToDb(DbEngine db) { … }
}

class DbInt {
     DbInt(String columnName) { … }
    
     public void saveToDb(DbEngine db) { …. }
}

Basically you create new Java types containing all the functionality you need. Depending on whether you’re using Java or Scala, you can bring in DB related functionality via inheritance, composition or mixins.

Alternatively, imagine if Java’s syntax allowed you to refer to fields of classes! You could replace types like DbSomeClass and DbInt with code like below which explicitly maps class fields to DB columns:

main() {
    // Hypothetical Java syntax
    DbEngine.map(SomeClass.field1, "Table1.Column1");

    DbEngine.persist(new SomeClass());
}

The salient feature of this last approach is that it physically separates the DB logic from the SomeClass file, while still allowing the compiler to observe the relationship. In this same way you can add functionality related to HTTP endpoints, logging etc. Which brings me to an almost forgotten aspect of the Java ecosystem: Aspects

Relation of annotations to Aspects

Aspect Oriented Programming is the label given to a programming technique where you add functionality to existing methods and fields in a physically different part of the code. In this article describing the usage of the AspectJ library, there is a “trace()” method which is defined to run whenever methods in a certain package are invoked (Figure 3 below):

Figure 3: Definition of an “aspect” method that runs whenever certain criteria are met

Just like annotations, Aspects also interfere with the visible control flow of a program. A profusion of them will also raise the same doubts in a programmer’s mind as annotations. They’re probably implemented using similar techniques as annotations: source code generation, byte code generation and manipulation. The main difference is that unlike annotations they don’t physically reside alongside the main code.

Backlash against annotations

Due to their loosely defined nature and their existence outside the Java syntax and type system, there has been considerable backlash against annotations and their indiscriminate use. For example, this talk titled “On Annotations: liberate yourself from demons“. Knowing the strengths and weaknesses of annotations should help in using them judiciously.

You can check out the References below for further reading.

References

  1. The case against annotations
  2. AspectJ Cheat Sheet 
  3. Talk: On Annotations
  4. JSR 269: Java Pluggable Annotation Processing API – link
  5. Annotation Types in the Java Language Spec – link
  6. Type Annotations and Pluggable type systems – link

Leave a Reply

Your email address will not be published. Required fields are marked *