BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News Null-Restricted and Nullable Types for Java

Null-Restricted and Nullable Types for Java

Earlier this week, we reported on the release of version 1.0.0 of the JSpecify project. This release focuses on providing type-use annotations to indicate the nullability status of usages of static types.

Related to this subject, Draft JEP 8303099 was recently made public. This JEP discusses Null-Restricted and Nullable Types, and aims to bring optional nullness-marking to the Java language.

The intent is to add markers - not just annotations - to a type use to specify whether the permissible value set for that use includes null or not. It is important to bear in mind that the proposal is in a very early stage of development (e.g. it doesn't have an official JEP number yet), so the syntax may well change. Having said this, for now Kotlin-like markers are used, so that, for a type Foo, there are three possibilities for how the type can be used:

  • Foo! is null-restricted - the acceptable values for this use of the type do not include null
  • Foo? is nullable - the acceptable values for this use of the type explicitly includes null
  • Foo does not specify whether or not null is acceptable

The use of bare Foo remains the default, as the meaning of existing code should not change when it's compiled.

Currently, the proposal calls for every type use to be annotated, i.e. there is no way to mark an entire class or module as null-restricted (as would be possible in JSpecify), although this capability may be added later.

This new feature introduces nullness conversions (similar to widening and unboxing conversions). For example, any of these sorts of assignment are permissible:

  • Foo! to Foo?
  • Foo! to Foo
  • Foo? to Foo
  • Foo to Foo?

as they represent a loosening of constraints - e.g. any null-restricted value can be represented in a nullable use of the type.

There are also narrowing nullness conversions, such as:

  • Foo? to Foo!
  • Foo to Foo!

These could cause runtime errors, e.g. by trying to load a null from a Foo? into a Foo!. The general strategy here is to treat these cases as compile-time warnings (but not errors) and to include a runtime check that throws a NullPointerException if the nullness bound if violated.

Note that these are basically the easy cases, and more complex cases are possible. For example, when dealing with generics the compiler may encounter situations such as type arguments whose nullness is inconsistent with their declared bounds.

The introduction of null markers provides additional compile-time safety, and allows for a gradual adoption of the markers - first by defining the nullness of types, and then seeking to eliminate compile-time warnings.

InfoQ spoke to Kevin Bourrillion (founder of Google's core libraries team and now a member of Oracle's Java language team) -- to get more details about the project.

InfoQ: Can you explain your background with the nullness efforts in Java?

Kevin Bourrillion: I co-founded the JSpecify group, and have been one of the main "designers" (defined as "person who bears the burden of driving insanely complicated design decisions to consensus somehow"). I've now moved to Oracle but remain involved in approximately the same ways.

InfoQ: How does this JEP overlap with the JSpecify work?

Bourrillion: Eventually we will have a Java with support for nullness markers. JSpecify has done the hard work of nailing down the semantic meanings of the annotations very precisely. This means that whatever upgrade timetable projects choose to adopt will be in a really good position to migrate from JSpecify to language-level nullness markers - in fact that migration should be highly automatable.

InfoQ: There seem to be some similarities between Draft JEP 8303099 and the way that generics was added, way back in Java 5. Is that accurate? This is largely a compile-time mechanism, which is mostly erased at bytecode level, isn't it?

Bourrillion: Yes, there are some useful parallels there. We think of type erasure and the spectre of "heap pollution" as being unfortunate concessions, but that is what made the feature so *adoptable*. That's why you almost never have to see a raw type anymore today (I hope!). Now in our case, null pollution will be part of our reality for a long time, but that's okay! Today it's all null pollution.

And yes, like generic type information, your nullness annotations are available at runtime via reflection, but are not involved in runtime type checking. I will be interested to see whether anyone builds a bytecode-instrumenter that injects null checks based on our annotations; I can see reasons that might be really useful and reasons it might not be worth the trouble; we'll have to see.

InfoQ: Null-restriction is also an important topic for Project Valhalla, isn't it? What can you share about the interaction between this Draft JEP and the ongoing work in that area?

Bourrillion: This is really the same question; Valhalla is just going to build on from that JEP draft you cited. Knowing what can't be null will help the VM optimize those indirections away.

InfoQ: In the mid to long term, JSpecify should be able to provide an on-ramp to language-level nullability support in Java. This is similar to how it can already be used to alignment with Kotlin's nullability support. How would you recommend readers go adopt adopting JSpecify today?

Bourrillion: It's just the JSpecify jar that's 1.0.0. The specification, which dictates the very precise meaning of the annotations, is still subject to (slight and subtle) changes. So if you put in a lot of work to annotate your codebase today, your code won't suddenly stop compiling correctly, but after some small spec revisions you might find you want to remove a couple `@Nullable`s here or add a few there.

If adopting nullness analysis in your toolchain today is just too time-consuming because of all the existing warnings you have to clean up, it's actually a perfectly reasonable approach to spray `@SuppressWarnings` however broadly you need! It's a bit ugly, but that's something you can just clean up incrementally over time. Even if that takes a long time, the point is that *new* code will start getting checked today, and that's the most important code to check anyway.

InfoQ: Thank you!

About the Author

Rate this Article

Adoption
Style

BT