Best practices in Java development circa 2021 dictate sparing use of null . Kotlin even makes you do extra work to declare a nullable reference type. In Kotlin, by default, variables with reference-type cannot have a null value.

Unfortunately we Geode developers maintain a lot of code that was not developed that way. As a result, as maintainers, we often find ourselves wondering whether a particular variable can take on a null value at runtime. One approach is to always assume every reference-typed variable can be null. But that leads to needlessly verbose code.

With GEODE-8882 we have introduced a compile-time dependency (in the geode-core subproject only) on the JetBrains annotations package. This makes the JetBrains @NotNull and @Nullable annotations available in classes in that subproject. If you need those annotations in other subprojects, just add this to the pertinent build.gradle:

compileOnly('org.jetbrains:annotations')

These annotations can help you declare your intentions in new code but they are particularly helpful during refactoring. If you wonder whether a variable or method parameter can ever be null, you can annotate it with @NotNull and IntelliJ will perform static analysis (inspection) and will point out (as a warning) places where that variable or parameter is taking on a potentially-null value.

Here are instructions from JetBrains on how the annotations work in the IDE: IntelliJ Support for @Nullable and @Not Null Annotations

If you set up your IDE this way then you will see IllegalArgumentExceptions when tests encounter violations of @NotNull at runtime. This runtime checking is not, in general, in effect in the Geode product unless the user goes out of their way to make it so, by adding a Java annotation processor. No such annotation processor is present in the Geode CI pipelines (at the time of this writing) so no runtime checking of this annotation happens in CI.

Here is the home page for the JetBrains annotations Maven project: https://github.com/JetBrains/java-annotations

In addition to those instructions you may want to tell IntelliJ inspections to "treat non-annotated members and parameters as @Nullable". This is the most conservative setting and should highlight the most bugs. Oddly, this setting is in the "Constant conditions & exceptions" panel under the Java "Probable bugs" inspection category:


You may also want to run the "Infer Nullity…" analyzer from the "Analysis" menu. You'll want to give IntelliJ lots of memory to work with (e.g. 8GiB) and it will take "a while" to complete. After that if you enable "Inferred annotations":

then you'll start to see these inferred annotations in gray in the editor. Here's an inferred @NotNull example:



Example Workflow

Here's an example. When working on SocketMessageWriter.writeHandshakeMessage I wondered if the clientVersion parameter could ever be null. Here we're testing for null . Could this null-check be eliminated?

To find out, I annotated the clientVersion parameter to writeHandshakeMessage this way (adding the @NotNull  annotation):

  public void writeHandshakeMessage(DataOutputStream dos, byte type, String p_msg,
      @NotNull final KnownVersion clientVersion, byte endpointType, int queueSize)
      throws IOException {

Here is the IDE highlighting a situation where an (indirect) caller may be passing null:

image

Under "More actions" I pick "Navigate to 'null' argument usages":

image

…where I find this call-site passing a null for clientVersion:

image

So that null-check in SocketMessageWriter.writeHandshakeMessage was needed after all! Since I didn't want to change null-sending call-site, and I didn't want to squander my analysis work, I marked the parameter @Nullable:

public void writeHandshakeMessage(DataOutputStream dos, byte type, String p_msg,
@Nullable KnownVersion clientVersion, byte endpointType, int queueSize)
throws IOException {

Now it's clear, without the need to look at the call tree, that this parameter can be null . So the null-check is important and should remain.

I could've found this without the @NotNull  annotation and IDE support. But in this case, I didn't find it until I leveraged the that support. I was able to leave behind a clue for my fellow maintainers. I'm hoping that clue might save them a little bit of work.

  • No labels