basic model - jspecify/jspecify Wiki

We believe a basic understanding of JSpecify nullness should be enough for most users to handle most situations. That "Basic Model" goes like this:

  • What Java does: Wherever references can flow through a program, a type is always specified in the code: a field type, method return type, etc. This type restricts which references are allowed through. Unfortunately, it always allows null!
  • What JSpecify does: In most of these places, JSpecify lets us add a simple extra bit of related information: "and is null okay here?"
  • What an analyzer does: A nullness analyzer uses that information in various smart ways to discover potential bugs: somewhere null could flow to unallowed, or somewhere that could try to dereference null and trigger a NullPointerException.
  • What I do: I fix that code to handle the null case, or I adjust the annotations wherever the value came from (or wherever it's going), or I deem the finding to be safe and suppress it.

To make these changes, I simply think about where I do or don't want null to be allowed to flow in my program, then follow some basic rules and recipes:

  • I mark my classes/packages/modules with @NullMarked.
  • If a parameter is at least sometimes willing to accept null, I mark it nullable.
  • If a method will at least sometimes return null, I mark its return type nullable.
  • When using a generic class like class List<T>, if I want to treat every unannotated appearance of T in that class as nullable at once, I write List<@Nullable Foo>.
  • When writing a generic class like class List<T>, if I want its users to have that List<@Nullable Foo> option available, I write the incomprehensible incantation class List<T extends @Nullable Object>.

If I hit a case where the Basic Model falls short, or I just crave a deeper understanding of what's really going on, it's time to read the type system model!