Java Type Annotations #4: Qualified Names and Nested Types

Marcio EndoMarcio EndoFeb 2, 2025

In the Java Programming language, type annotations are those which can be applied to the use of a type. We use types whenever we declare a field in a Java class, for example. As a result, we can use type annotations to annotate the type of a field declaration.

Types in Java can be nested. In other words, we can write a type declaration within the body of another type declaration. For example, the java.util.Map interface declares the nested Entry interface. The latter represents a map entry, i.e., a single key-value mapping.

When referring to a type, we can use a simple name or we can use a qualified name. For example, if we import the java.util.Map.Entry type we can use its simple name Entry when referring to it. If, on the other hand, we import its enclosing type java.util.Map, we can use the Map.Entry qualified name.

In this blog post, we'll discuss the interaction between all the forementioned elements.

Example

If X is an annotation which targets types only, we can use it to annotate the type of a field declaration which stores a java.util.Map.Entry instance:

private @X Entry<String, LocalDate> entry;

The X annotation applies to the type of field, namely the Entry<String, LocalDate> type, a map entry whose keys, of the String type, are mapped to values of the LocalDate type.

Let's suppose that, instead of referring to the Entry type by its simple name, we'd like to use the Map.Entry qualified name instead. It could be that our program declares a Calendar.Entry type; and we want to make it clearer that our entry field is a map entry, not a calendar entry.

So, we change our field declaration to the following:

// compilation error!private @X Map.Entry<String, LocalDate> entry;

Our field declaration now fails to compile! Here's the JDK 23 compiler error message:

TypeAnnotations4.java:23: error: type annotation @X is not expected here
  private @X Map.Entry<String, LocalDate> entry;
                      ^
  (to annotate a qualified type, write Map.@X Entry<String,LocalDate>)
1 error

As per the error message, we should move the annotation immediately before the Entry simple name:

private Map.@X Entry<String, LocalDate> entry;

Our field declaration now compiles without errors.

Inner Classes

From the example of the previous section, it's possible for one to wrongly assume that the enclosing type cannot be annotated. But the enclosing type can be annotated: when the nested type is an inner class, i.e, a non-static nested class.

Suppose we have the following class hierarchy:

class Top {    class Inner {}  }

In the code above:

  • Top is a top-level class.

  • Inner is an inner class relative to Top.

If X and Y are both type annotations, all of the following are valid field declarations:

private @X Top.Inner inner1;private Top.@Y Inner inner2;private @X Top.@Y Inner inner3;

In other words, we can annotate:

  • The enclosing class only.

  • The inner class only.

  • Both the enclosing and inner classes.

Why are we allowed to annotated both types when dealing with inner classes?

Why?

An instance of an inner class is always associated to an instance of its enclosing class. Putting it differently, to create an instance of an inner class, we must first create an instance of its outer class:

Top top = new Top();Inner inner = top.new Inner();

Therefore, whenever we use the type of an inner class, we always have two distinct types:

  • The type of the instance of inner class itself.

  • The type of the associated instance of its enclosing (outer) class.

And, since we are using two distinct types, we can annotate each one separately.

Fully Qualified Names

At times, we must refer to a type by its fully qualified name. It is required, for example, if we use, in the same compilation unit, two distinct types with the same simple name.

One classical example is using both java.util.Date and java.sql.Date in the same compilation unit. Granted, the introduction of the Date/Time API might have, hopefully, reduced its occurrence. But it still works fine as an example:

import java.util.Date;public class Example {  private Date simple;    private java.sql.Date full;}

In the Example class above, we have two fields of two distinct types:

  • The type of the simple field was declared with a simple name Date. From the import declaration, we know that the full name of its type is java.util.Date.

  • The type of the full field, on the other hand, was declared with the fully qualified name java.sql.Date.

Next, using our X annotation, let's annotate the types of both of our fields. At first, we might think that the following would be correct:

// compilation error!private @X Date simple;private @X java.sql.Date full;

But our second field fails to compile. Here's the JDK 23 compiler error message:

TypeAnnotations4.java:45: error: type annotation @X is not expected here
  private @X java.sql.Date full;
                     ^
  (to annotate a qualified type, write java.sql.@X Date)
1 error

As per the error message, we should declare our second field like so:

private @X Date simple;private java.sql.@X Date full;

The annotation goes immediately before the simple name. Our field declaration now compiles without errors.

Conclusion

Type annotations always refer to the type that is closest to the annotation.

If we use a qualified name to refer to a static nested type, such as Map.Entry, then Map in this context does not denote a type: it is just a name. Therefore, to annotate the Map.Entry type we write:

  • Map.@X Entry.

Similarly, to annotate a type referred by its fully qualified name, we must place the annotation immediately before the simple name. In other words, we must not annotate the package name; we should write:

  • java.sql.@X Date.

When referring to the type of an inner class, we are implicitly referring to two distinct types: the type of the inner class itself and the type of the outer class. It means we can write:

  • Top.@Y Inner.

  • @X Top.Inner.

  • @X Top. @Y Inner.

All these rules are specified in the Java Language Specification (JLS).

You can find the source code used in this blog post in this Gist.