Java Type Annotations #6: Receiver Parameter

Marcio EndoMarcio EndoFeb 16, 2025

In the Java Programming language, type annotations are those which annotates the use of a type. When we invoke an object's method, we use at least one type: the type of the object on which the method is invoked.

From the two statements above, we conclude that we can annotate this use of a type with a type annotation. For this purpose, the language provides the receiver parameter.

Example

First, let's declare a type annotation X.

@Target(ElementType.TYPE_USE)@Retention(RetentionPolicy.RUNTIME)@interface X {}

Our annotation targets uses of a type and its value is retained at runtime. We'll use it in the following manner:

static class Receiver {  @Override  public String toString(@X Receiver this) {    return "Receiver Parameter";  }}

We have declared a Receiver class, which is a static nested class of the implicitly declared class. Our class declares a single toString method. The method declares a single parameter whose type is Receiver and whose name is the reserved keyword this. Finally, the type of the method parameter is annotated with the X annotation defined earlier.

Our Receiver class compiles without errors. The parameter of the toString method is formally called the receiver parameter. It is not a formal parameter and its single purpose is to allow the type of the object on which a method is invoked to be annotated.

Verifying the Annotation at Runtime

Let's write a program to inspect the receiver parameter we've just declared. We will try to confirm that it is annotated with the X annotation. Additionally, we want to confirm that the method we've declared overrides the Object::toString method. In other words, we want to confirm is not an overload.

We write the following:

void main() throws NoSuchMethodException {  Class<?> type;  type = Receiver.class; // 1  Method method;  method = type.getDeclaredMethod("toString"); // 2  AnnotatedType recType;  recType = method.getAnnotatedReceiverType(); // 3  System.out.println("Annotations on receiver:");  for (Annotation annotation : recType.getDeclaredAnnotations()) {    System.out.println(annotation); // 4  }    System.out.println(new Receiver()); // 5}

In our main method:

  1. We obtain the Class object of our Receiver class.

  2. From the Class object, we obtain the Method object representing the toString method.

  3. Using the Method object, we obtain the annotated type of the receiver parameter.

  4. We iterate over the annotations on the receiver parameter's type and print their names.

  5. We indirectly invoke the toString method of a new instance of the Receiver class.

When we execute this program, it produces the following output:

Annotations on receiver:
@TypeAnnotations6.X()
Receiver Parameter

We observe that:

  • The type of the receiver parameter is annotated with the X annotation.

  • The toString we've declared overrides the Object::toString method.

In other words, we've annotated the type of the receiver of the toString method call.

Inner Classes

As a final note, we can declare a receiver parameter on a constructor, provided it belongs to an inner class:

static class Outer {  class Inner {    Inner(@X Outer Outer.this) {}  }}

The receiver parameter, in this case, represents the enclosing instance to which the newly constructed instance is associated.

Conclusion

When we invoke a method on an object we are using at least one type: the type of the object on which the method is invoked. We can annotate this use of a type by declaring a receiver parameter on the method.

It is worth noting that the receiver parameter is not a formal parameter. It is just a syntactic construct to allow the use of type annotations. You can read more about the receiver parameter in this blog post.

Finally, you can find the source code used in this blog post in this Gist.