What is the purpose of having annotation tmesis/inside Java qualified types?

Viewed 399

Does this have a purpose static @NotNull @Other My.@NotNull @Other Builder createBuilder()

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

public class A {
    static class My {
        static class Builder {
            public My build() {
                return new My(); } } }

    @Target({ElementType.METHOD, ElementType.TYPE_USE})
    public @interface NotNull { }

    @Target({ElementType.METHOD, ElementType.TYPE_USE})
    public @interface Other { }

    public static @NotNull @Other My.@NotNull @Other Builder createBuilder() {
        return new My.Builder();
    }
}
1 Answers

The main reason to use such a construct would be backward compatibility.

Prior to Java 8, there were no type annotations, so method annotations were often used to actually described the return type of a method like

@Target(ElementType.METHOD)
public @interface NotNull { }

@Target(ElementType.METHOD)
public @interface Other { }

public static @NotNull @Other My.Builder createBuilder() {
    return new My.Builder();
}

Starting with Java 8, you can annotate the return type itself and that’s what you normally would do. But to support old tools still looking for method annotations, you can keep the @Target METHOD. For return types consisting of a simple name, the code location for method annotations and return type annotations is the same, so you can create a method annotation and a return type annotation at the same time with a single occurrence, i.e.

@MethodAndTypeAnnotation ReturnType method() …

However, for qualified names, the syntax is different, as type annotation must be placed immediately prior to the simple name of the annotated element, i.e.

@Target(ElementType.TYPE_USE)
public @interface NotNull { }

@Target(ElementType.TYPE_USE)
public @interface Other { }

public static My.@NotNull @Other Builder createBuilder() {
    return new My.Builder();
}

For a pure type annotation using public static @NotNull @Other My.Builder createBuilder() would cause a compiler error, as it would be an attempt to annotate My which only serves as a qualifier here, not an actual type use. Note that if Builder was an inner class rather than a nested class (i.e. not static), it would be legal to annotate the outer type, though unlikely to be desired.

So in your case

@Target({ElementType.METHOD, ElementType.TYPE_USE})
public @interface NotNull { }

@Target({ElementType.METHOD, ElementType.TYPE_USE})
public @interface Other { }

//            method annotation    type annotation
public static @NotNull @Other   My.@NotNull @Other Builder createBuilder() {
    return new My.Builder();
}

both occurrences are required to annotate the method and the return type. As said, if the Builder was an inner class, the first occurrence would be legal to annotate the outer type and it would annotate it whether you want or not.

So in general, it’s not recommended to mix the “type use” annotation scope with others but to update code processing tools still requiring method or field annotations for tasks that are actually a type annotation’s job.


As said, for simple names you can annotate the method and return type at once, which also works for nested types when you use an import statement. But this requires the top level class to be in a package:

package example;

import example.A.My.Builder;

public class A {
    static class My {
        static class Builder {
            public My build() {
                return new My(); } } }

    @Target({ElementType.METHOD, ElementType.TYPE_USE})
    public @interface NotNull { }

    @Target({ElementType.METHOD, ElementType.TYPE_USE})
    public @interface Other { }

    public static @NotNull @Other Builder createBuilder() {
        return new My.Builder();
    }
}
Related