Annotations are just modifiers in Java (syntactically speaking)

Marcio EndoMarcio EndoDec 30, 2024

Annotations add pieces of information to elements of a Java program. We can annotate a class declaration, a method declaration, a field declaration, and other declarations. We can also annotate uses of a type, such as the type in a throws clause. The annotations in a Java program are typically inspected by a tool, such as the Java compiler, or by a library.

While tools and libraries may assign meaning to annotations, to the Java language's grammar, and when used on declarations, annotations are no more than element modifiers. This is what we'll discuss in this blog post.

Let's begin.

Example

When annotating a program element, a method declaration for example, it is common to write the annotation at the very start of the declaration:

@Overridepublic final String toString() {  return "Annotations are just modifiers";}

See how the annotation sits on top of the method in the example? I always thought that was the only possible way to annotate a method. It turns out I was wrong; the Override annotation can go between the public and the final modifiers:

public @Override final String toString() {  return "Annotations are just modifiers";}

Or, if you prefer, the Override annotation can go just before the return type of the method:

public final @Override String toString() {  return "Annotations are just modifiers";}

I used to think that the last two examples would fail to compile, but they do not; all three declarations are syntactically correct and they are all equivalent.

It's all in the JLS

It is all specified in the Java Language Specification (JLS). Method declarations are defined in Section 8.4. This section defines a number of productions. We are interested in the MethodDeclaration production:

MethodDeclaration:  
  {MethodModifier} MethodHeader MethodBody  

The curly brackets indicate that the enclosed content can occur "zero or more" times. So the MethodDeclaration production specifies that a method declaration begins with zero or more method modifiers.

A little later, in Section 8.4.3, we find the definition for method modifiers. It defines the MethodModifier production:

MethodModifier:  
  (one of)  
  Annotation public protected private  
  abstract static final synchronized native strictfp  

See how Annotation is included here? In other words, all the listed items are considered method modifiers by the language's grammar, and, as shown in the examples from the previous section, they can appear in any order.

It is the same with other declarations

It works the same with other declarations:

  • Class declarations (including enum declarations and record declarations)

  • Interface declarations (including annotation interface declarations)

  • Constructor declarations

  • Field declarations

  • Formal parameter declarations

  • Local variable declarations

As an example, let's look at annotation interface declarations. They are defined in Section 9.6 of the JLS:

AnnotationInterfaceDeclaration:
  {InterfaceModifier} @ interface TypeIdentifier AnnotationInterfaceBody

Modifiers come before the '@' character. This production reuses the (regular) interface modifiers defined in Section 9.1.1:

InterfaceModifier:
  (one of)
  Annotation public protected private
  abstract static sealed non-sealed strictfp

Just like the method declaration modifiers, the interface declaration modifiers production includes the Annotation production. Therefore, the following annotation declaration:

@Retention(RetentionPolicy.SOURCE)@Target({    ElementType.TYPE,    ElementType.CONSTRUCTOR,    ElementType.FIELD})public @interface MyAnnotation {}

Is equivalent to the following declaration:

@Retention(RetentionPolicy.SOURCE)public @Target({    ElementType.TYPE,    ElementType.CONSTRUCTOR,    ElementType.FIELD}) @interface MyAnnotation {}

Here, the public modifier was placed between the Retention and Target annotations. It compiles without errors.

Just because you can doesn't mean you should

As a recommendation, it’s best to:

  • Write declaration annotations before all other modifiers.

  • Write type annotations immediately before the type they apply to.

For example, consider this field declaration:

@First@Secondprivate final @Third String value;

If our codebase follows idiomatic conventions, we can safely assume that:

  • The @First and @Second annotations apply to the field declaration.

  • The @Third annotation applies to the type of the field.

This guidance is even spelled out in Section 9.7.4 of the JLS, titled "Where Annotations May Appear":

"It is customary, though not required, to write declaration annotations before all other modifiers, and type annotations immediately before the type to which they apply."

Following this convention makes it clearer to readers what each annotation targets.

Conclusion

To the Java compiler, annotations are just modifiers. So, in a method declaration, for example, an annotation may appear before, after, or in between the regular modifiers.

However, just because you can write annotations in any position relative to the other modifiers, it doesn't mean that you should. It’s best to follow the idiomatic forms of the language:

  • Write declaration annotations before all other modifiers.

  • Write type annotations immediately before the type they apply to.

While the position of annotations may be irrelevant to the Java compiler, it matters to the readers of our code. And writing readable code should always be our priority.