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.

(Bild: Erstellt mit KI)
- Hendrik Ebbers
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.
Breite Zusammenarbeit fĂĽr JSpecify
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.
Vorteile der Annotationen
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 NullPointerException
s zur Laufzeit vermeiden.
Kein Allheilmittel
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.
Nächste Schritte zu Best Practices und einem Standard
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)