Annotation Interface NullMarked


Indicates that the annotated element and the code transitively enclosed within it are null-marked code: there, type usages are generally considered to exclude null as a value unless specified otherwise. Using this annotation avoids the need to write @NonNull many times throughout your code.

For a comprehensive introduction to JSpecify, please see jspecify.org.

Effects of being null-marked

Within null-marked code, as a general rule, a type usage is considered non-null (to exclude null as a value) unless explicitly annotated as Nullable. However, there are several special cases to address.

Special cases

Within null-marked code:

  • We might expect the type represented by a wildcard (like the ? in List<?>) to be non-null, but it isn't necessarily. It's non-null only if it extends a non-null type (like in List<? extends String>), or if the class in use accepts only non-null type arguments (such as if List were declared as class List<E extends String>). But if List does accept nullable type arguments, then the wildcards seen in List<?> and List<? super String> must include null, because they have no "upper bound". (Why?)
    • Conversely, a type parameter is always considered to have an upper bound; when none is given explicitly, Object is filled in by the compiler. The example class MyList<E> is interpreted identically to class MyList<E extends Object>: in both cases the type argument in MyList<@Nullable Foo> is out-of-bounds, so the list elements are always non-null. (Why?)
  • Otherwise, being null-marked has no consequence for any type usage where @Nullable and @NonNull are not applicable, such as the root type of a local variable declaration.
  • When a type variable has a nullable upper bound, such as the E in class Foo<E extends @Nullable Bar>), an unannotated usage of this type variable is not considered nullable, non-null, or even of "unspecified" nullness. Rather it has parametric nullness. In order to support both nullable and non-null type arguments safely, the E type itself must be handled strictly: as if nullable when "read from", but as if non-null when "written to". (Contrast with class Foo<E extends Bar>, where usages of E are simply non-null, just like usages of String are.)
  • By using NullUnmarked, an element within null-marked code can be excluded and made null-unmarked, exactly as if there were no enclosing @NullMarked element at all.

Where it can be used

@NullMarked and @NullUnmarked can be used on any package, class, method, or constructor declaration; @NullMarked can be used on a module declaration as well. Special considerations:
  • To apply this annotation to an entire (single) package, create a package-info.java file there. This is recommended so that newly-created classes will be null-marked by default. This annotation has no effect on "subpackages". Warning: if the package does not belong to a module, be very careful: it can easily happen that different versions of the package-info file are seen and used in different circumstances, causing the same classes to be interpreted inconsistently. For example, a package-info file from a test source path might hide the corresponding one from the main source path, or generated code might be compiled without seeing a package-info file at all.
  • Although Java permits it to be applied to a record component declaration (as in record Foo(@NullMarked String bar) {...}), this annotation has no meaning when used in that way.
  • Applying this annotation to an instance method of a generic class is acceptable, but is not recommended because it can lead to some confusing situations.
  • An advantage of Java modules is that you can make a lot of code null-marked with just a single annotation (before the module keyword). NullUnmarked is not supported on modules, since it's already the default.
  • If both @NullMarked and @NullUnmarked appear together on the same element, neither one is recognized.