Null Problemo: Bessere Null-Checks in Java mit JSpecify

Das Open-Source-Projekt JSpecify zielt auf einheitlichen Standard für Null-Annotationen in Java. Beteiligt sind Firmen wie Google, JetBrains und Microsoft.

In Pocket speichern vorlesen Druckansicht 29 Kommentare lesen
Ein Wecker, der vor "Null" warnt

(Bild: Erstellt mit KI)

Lesezeit: 5 Min.
Von
  • Hendrik Ebbers
Inhaltsverzeichnis

Vor einem Jahr habe ich bereits über Null-Checks in Java geschrieben. Nach wie vor ist es sinnvoll, Parameter von Methoden und Konstruktoren mit Annotationen bezüglich des Verhaltens von null (z.B. @NonNull) zu versehen. Mittlerweile ist der Support jedoch deutlich besser geworden, da vor Kurzem Version 1.0 von JSpecify erschienen ist. Das möchte ich für ein Update zu dem Thema nutzen.

Neuigkeiten von der Insel - Hendrik Ebbers

Hendrik Ebbers (@hendrikEbbers) ist Java Champion, JCP Expert Group Member und wurde mehrfach als Rockstar-Speaker der JavaOne ausgezeichnet. Mit seinem eigenen Unternehmen Open Elements hilft Hendrik aktuell den Hedera Hashgraph zu gestalten und dessen Services der Öffentlichkeit zugänglich zu machen. Hendrik ist Mitgründer der JUG Dortmund sowie der Cyberland und gibt auf der ganzen Welt Vorträge und Workshop zum Thema Java. Sein Buch "Mastering JavaFX 8 Controls" ist 2014 bei Oracle Press erschienen. Hendrik arbeitet aktiv an Open Source Projekten wie beispielsweise JakartaEE oder Eclipse Adoptium mit. Hendrik ist Mitglied des AdoptOpenJDK TSC und der Eclipse Adoptium WG.

JSpecify ist ein Open-Source-Projekt, in dem sich die bisherigen Anbieter von Null-Handling-Annotationen zusammengeschlossen haben, um endlich einen nutzbaren Standard zu definieren. Dazu gehören unter anderem Google, JetBrains, Meta, Microsoft und Oracle. JSpecify ist ein vollwertiges Modul im Java-Modulsystem, hat keine eigenen Abhängigkeiten und liefert mit gerade einmal vier Annotationen alles, was man in einem modernen Java-Projekt benötigt, um das Handling von null bei Parametern zu spezifizieren. Beispielcode, der die Annotationen nutzt, könnte wie folgt aussehen:

static @Nullable String emptyToNull(@NonNull String x) {
    return x.isEmpty() ? null : x;
}

static @NonNull String nullToEmpty(@Nullable String x) {
    return x == null ? "" : x;
}

Mehr Codebeispiele finden sich im User-Guide von JSpecify.

Das reine Anbringen der JSpecify-Annotation hat allerdings wenig Effekt. Der Compiler übersetzt weiterhin Code, der null an einen mit @NonNull-annotierten Parameter übergibt, und bei der Laufzeit löst der übersetzte Code nicht automatisch eine Exception aus.

Der Vorteil der Annotationen zeigt sich unter anderem im Zusammenspiel mit Entwicklungsumgebungen. IntelliJ kann die Annotations erkennen und Warnungen oder Fehler bei Code anzeigen, der die Annotationen verletzt. Will man auf Nummer sicher gehen und Code mit solchen Problemen überhaupt nicht zulassen, kann man zusätzliche Hilfsmittel verwenden. Das von Uber entwickelte Open-Source-Tool NullAway kann diese Annotationen zur Build-Zeit überprüfen und Fehler auszulösen, wenn die Definition der Annotationen nicht eingehalten wird. Fügt man das Ganze zu seinem Gradle- oder Maven-Build hinzu, erfolgt beim Kompilieren automatisch einen Fehler:

error: [NullAway] passing @Nullable parameter 'null' where @NonNull is required
   myMethod(null);
          ^

Mit dieser Toolchain kann man seinen Code um einiges robuster bekommen und NullPointerExceptions zur Laufzeit vermeiden.

Muss man sich keine Gedanken mehr über NullPointerExceptions machen? So einfach ist es leider nicht. Diese Maßnahmen können nur den eigenen Code überprüfen. Hat man Abhängigkeiten, die keine solchen Annotationen nutzen, kann man nicht wissen, ob man diesen als Parameter null übergeben kann und welches Verhalten dies auslöst. Daher ist es weiterhin wichtig, an verschiedenen Stellen Variablen auf null zu überprüfen.

Wer Libraries oder Code entwickelt, der von anderen Projekten aufgerufen wird, kann nicht sicherstellen, dass Nutzer sich an die definierten Regeln halten und an einen mit @NonNull annotierten Parameter auch wirklich kein null übergeben. Daher ist es wichtig, immer Null-Checks durchzuführen, wenn man den Kontext des eigenen Codes verlässt – egal ob bei eigenen Abhängigkeiten oder bei einer öffentlichen API.

Dazu ist das aus dem OpenJDK stammende java.util.Objects.requireNonNull(obj, message) weiterhin das Mittel der Wahl. Um immer sinnvolle Exceptions zu erstellen, sollte man auf die Variante mit dem Message-Parameter setzen, da das System sonst eine NullPointerException ohne Message wirft. Das Ganze sieht für eine öffentliche API folgendermaßen aus:

public void setName(@NonNull String name) {
   this.name = Objects.requireNonNull(name, “name must not be null”);
}

Wer einem performancekritischen Umfeld arbeitet, sollte auf eigene Methoden für die Checks verzichten. Der JIT-Compiler behandelt Objects.requireNonNull(...) durch die Annotation @ForceInline besonders und fügt alle Aufrufe der Methode direkt in die aufrufende Methode ein (inline), um so Performance und Stack-Größe zu optimieren.

Es hat lange gedauert, bis die Java-Community den heutigen Stand erreicht hat und es eine saubere und sinnvolle Bibliothek mit Annotationen bezüglich Null-Handling gibt. Was als JSR305 im Jahr 2006 gestartet und leider schnell wieder fallengelassen wurde, könnte sich nach vielen Problemen mit unterschiedlichsten Annotationen und Umsetzungen zu einem De-facto-Standard wie SLF4J (Simple Logging Facade for Java) entwickeln.

JSpecify geht hier ganz klar den richtigen Weg. Toll wäre es, wenn nun ein Tooling wie beispielsweise NullAway sich durchsetzt und mit einer einfachen Nutzung und Best Practices es quasi jedem Projekt ermöglicht, besser mit null umzugehen. Wer bisher die Annotationen und Tools wie NullAway noch nicht im Einsatz hat, sollte sie ausprobieren. Jetzt ist der richtige Moment, um damit zu starten.

Anmerkung: Parallel zum Schreiben dieses Beitrags ist im OpenJDK mit einem neuen JEP ein besserer nativer Support angekündigt worden. Da es noch einige Zeit dauern wird, bis die im JEP diskutierten Features Einzug in eine LTS Version des OpenJDK haben werden, sind die hier beschriebenen Mittel und Tools weiterhin eine klare Empfehlung. Das JEP bietet aber genug Aspekte, um es zeitnah in einem Artikel genauer zu betrachten.

(rme)