What's the meaning of java.util.@Nullable?

The line public static <T> java.util.@Nullable Optional<T> toJavaUtil is written like this, because the usual style public static <T> @Nullable java.util.Optional<T> toJavaUtil is invalid. This is defined in the JLS §9.7.4:

It is a compile-time error if an annotation of type T applies to a type (or any part of a type) in a type context, and T is applicable in type contexts, and the annotation is not admissible.

For example, assume an annotation type TA which is meta-annotated with just @Target(ElementType.TYPE_USE). The terms @TA java.lang.Object and java.@TA lang.Object are illegal because the simple name to which @TA is closest is classified as a package name. On the other hand, java.lang.@TA Object is legal.

The type declaration of org.checkerframework.checker.nullness.qual@Nullable is:

@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})

So it is applying to this rule.

That this structure doesn't break the execution, since package java.util and class name Optional were split, can be seen when we look at the compiled code using javap -c [compiled class name]:

class just.a.test.Main {
  just.a.test.Main();
    Code:
       0: aload_0
       1: invokespecial #1          // Method java/lang/Object."<init>":()V
       4: return

  public static <T> java.util.Optional<T> toJavaUtil(blub.Optional<T>);
    Code:
       0: aload_0
       1: ifnonnull     8
       4: aconst_null
       5: goto          12
       8: aload_0
       9: invokevirtual #2          // Method blub/Optional.toJavaUtil:()Ljava/util/Optional;
      12: areturn
}

(blub.Optional is a local class where I copied the Guava code in, in order to get a minimal example to de-/compile)

As you can see, the annotation doesn't exist there anymore. It is only a marker for the compiler to prevent a warning when the method returns null (and a hint for the source code readers), but it won't be included in the compiled code.


This compiler error also applies to variables like:

private @Nullable2 java.util.Optional<?> o;

But can become acceptable when the annotation additionally gets the target type ElementType.FIELD, as written in the same JLS clause:

If TA is additionally meta-annotated with @Target(ElementType.FIELD), then the term @TA java.lang.Object is legal in locations which are both declaration and type contexts, such as a field declaration @TA java.lang.Object f;. Here, @TA is deemed to apply to the declaration of f (and not to the type java.lang.Object) because TA is applicable in the field declaration context.


When using annotations, this is the syntax that is used when you want to write fully qualified name for the type, instead of adding import statement.

Quoting from the checker framework manual:

The correct Java syntax to write an annotation on a fully-qualified type name is to put the annotation on the simple name part, as in java.util.@Nullable List. But, it’s usually better to add import java.util.List to your source file, so that you can just write @Nullable List.

It is also mentioned on page 2 of JSR308 specification which can be downloaded here. It says:

A type annotation appears before the type’s simple name, as in @NonNull String or java.lang.@NonNull String.


The strange bit here really is the unfamiliar syntax for applying a ElementType.TYPE_USE-targeted annotation. If you check the Nullable's docs, you'll see the unfamiliar target:

...
@Target(value={TYPE_USE,TYPE_PARAMETER})
<public @interface Nullable
...

This annotation is used right before the simple name of the annotated type, like in both of the following:

public static <T> @Nullable Optional<T> toJavaUtil
public static <T> java.util.@Nullable Optional<T> toJavaUtil

I didn't know what the use of this kind of target was, so after a quick read, I got to this simple example, which picks up return type metadata using an annotation that has that target:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_USE)
@interface InterceptedReturnValue {
    boolean value() default true;
}

And processed it using:

public java.lang.@InterceptedReturnValue(true) String testMethod(String param) {
    return null;
}

public @InterceptedReturnValue(false) String testMethod(int param) {
    return null;
}

public static void main(String[] args) throws Exception {
    Method m = Main.class.getDeclaredMethod("testMethod", String.class);
    if(m.getAnnotatedReturnType().isAnnotationPresent(InterceptedReturnValue.class)) {
        InterceptedReturnValue config = m.getAnnotatedReturnType()
                .getAnnotation(InterceptedReturnValue.class);

        if(config.value()) {
            //logging return value enabled
        }
    }
}

I'm sure many useful frameworks, such as checkerframework, make the most appropriate use of ElementType.TYPE_USE

Tags:

Java