Ausnahmen vom Typ NullPointerException bleiben bis heute ein lästiges Problem. Glücklicherweise können statische Analysewerkzeuge und moderne IDEs heute zuverlässig prüfen, ob ein Ausdruck dereferenziert wird, der möglicherweise null ist. Problematisch bleiben dabei vor allem Bibliotheken, deren Null-Verhalten nicht präzise beschrieben ist. Aus diesem Grund haben sich Null-Annotationen etabliert, mit denen sich ausdrücken lässt, ob ein Typ null sein darf oder nicht.
JSpecify
Ab Spring Framework 7 setzt Spring für Null-Safety auf JSpecify (https://jspecify.dev/). JSpecify ist ein gemeinsames Standardisierungsprojekt, das unter anderem von Google, JetBrains (IntelliJ) und dem Spring-Team vorangetrieben wird. Ziel ist eine einheitliche, werkzeugübergreifende Semantik für Null-Sicherheit in Java, im Gegensatz zu älteren Ansätzen wie JSR-305, die nie konsistent umgesetzt wurden.
JSpecify deklariert im Paket org.jspecify.annotations folgende Annotationstypen:
§ @NullMarked: Definiert einen Geltungsbereich (Paket, Klasse, Methode, Modul), in dem alle nicht annotierten Typverwendungen standardmäßig als nicht-null gelten.
§ @Nullable: Markiert eine Typverwendung explizit als null zulässig, also als Wert, der null sein darf. Wird verwendet, um Ausnahmen innerhalb eines @NullMarked-Kontexts zu kennzeichnen.
§ @NonNull: Markiert eine Typverwendung explizit als nicht-null. Vor allem sinnvoll außerhalb eines @NullMarked-Kontexts oder zur bewussten Hervorhebung.
§ @NullUnmarked: Hebt die Wirkung von @NullMarked innerhalb eines Bereichs auf. Nicht annotierte Typverwendungen haben dort wieder keine definierte Null-Semantik. Wird primär für Migration bestehender Codebasen genutzt.
Hinweis
Die Annotationen stammen aus dem Artefakt org.jspecify:jspecify. In Spring Framework 7 ist diese Abhängigkeit in spring-core als API-Abhängigkeit enthalten und in typischen Projekten transitiv verfügbar. Kotlin ab Version 2.1 wertet Annotationen aus org.jspecify.annotations strikt aus.
Prüfung durch Tools
Entwicklungsumgebungen wie IntelliJ IDEA sowie statische Analysewerkzeuge können JSpecify-Annotationen auswerten und auf potenzielle NullPointerException-Risiken hinweisen. Spring Framework verwendet ab Version 7 im Build-Prozess zusätzlich das Analysewerkzeug NullAway (https://github.com/uber/nullaway). NullAway ist ein Compile-Time-Checker auf Basis von Google Error Prone (https://github.com/google/error-prone), der die Einhaltung von Null-Verträgen strikt überprüft. Verstöße – etwa das Zurückgeben von null trotz nicht-null Annotation oder die Übergabe von null an nicht-null Parameter – führen dabei zu Build-Fehlern.
Auf diese Weise stellt Spring sicher, dass die eigene API konsistent annotiert ist und keine versteckten Null-Probleme enthält.
Null-Prüfungen in eigenen Projekten
Dieselbe Art der Prüfung lässt sich auch in eigenen Projekten integrieren, um Null-Fehler frühzeitig und reproduzierbar im Build zu erkennen.
Um @NullMarked auf Paketebene zu aktivieren, legt man eine package-info.java an:
@NullMarked
package com.tutego.date4u.core;
import org.jspecify.annotations.NullMarked;
Damit werden in diesem Paket nicht annotierte Typverwendungen standardmäßig als nicht-null interpretiert, sofern nicht explizit @Nullable verwendet wird.
Frühere Spring-Annotationen
Spring stellte über viele Jahre eigene Null-Annotationen im Paket org.springframework.lang[1] bereit. Diese sind seit Spring Framework 7 jedoch veraltet und werden durch JSpecify ersetzt. Die frühere Semantik lässt sich grob wie folgt abbilden:
§ @NonNullApi entspricht funktional einem @NullMarked auf Paketebene
§ @NonNullFields wird ebenfalls durch @NullMarked abgedeckt
§ @Nullable bleibt in gleicher Bedeutung erhalten
§ @NonNull kann weiterhin zur expliziten Kennzeichnung verwendet werden
Dabei handelt es sich jedoch nicht um eine rein mechanische Ersetzung. Während die Spring-Annotationen pauschal auf Parameter, Rückgabewerte und Felder wirkten, basiert JSpecify auf einer präziseren Typsemantik: Annotationen beziehen sich auf konkrete Typverwendungen. Dadurch lassen sich insbesondere generische Typen sowie Array- und Vararg-Elemente präziser beschreiben.
Für bestehende Codebasen kann @NullUnmarked verwendet werden, um noch nicht migrierte Bereiche vorübergehend vom @NullMarked-Defaulting auszunehmen und die Migration schrittweise durchzuführen.
[1] https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/lang/package-summary.html
Drei meiner Bücher, echt????