Das Symptom
Der Entwickler sieht eine NullPointerException mit Zeilennummer, und in der Zeile stehen drei verkettete Aufrufe: kunde.getAdresse().getStadt().length(). Weil Java ja angeblich nicht verrät, welche Referenz null ist, baut er Debug-Ausgaben ein, zerlegt die Zeile in vier Zeilen oder setzt Haltepunkte, um den Schuldigen zu finden. Dabei steht die Antwort seit Jahren in der Meldung selbst. Der Glaubenssatz stammt aus zwei Jahrzehnten, in denen er wahr war, und wird weitergegeben, ohne die aktuelle Meldung je gelesen zu haben.
Die „Layer 8“-Wahrheit
Die Beschreibung des java-Kommandos für Java SE 21 erklärt die Option -XX:+ShowCodeDetailsInExceptionMessages so: „Enables printing of improved NullPointerException messages. When an application throws a NullPointerException, the option enables the JVM to analyze the program’s bytecode instructions to determine precisely which reference is null, and describes the source with a null-detail message.“ Die JVM liest also den Bytecode der Stelle, an der es krachte, und schreibt hin, welcher Rückgabewert, welches Feld oder welches Arrayelement null war.
Wo diese Information landet, steht im nächsten Satz: „The null-detail message is calculated and returned by NullPointerException.getMessage(), and will be printed as the exception message along with the method, filename, and line number.“ Und der entscheidende Satz der Beschreibung ist der letzte: „By default, this option is enabled.“ Niemand muss etwas einschalten. Wer heute eine NullPointerException ohne Detailtext sieht, läuft entweder auf einer sehr alten Laufzeit, hat die Option ausdrücklich abgeschaltet, oder die Exception wurde im Code mit new NullPointerException() selbst erzeugt, wofür die JVM keine Analyse macht. Ab welcher Version die Option standardmäßig aktiv ist, sagt die Beschreibung für Java SE 21 nicht; die Beschreibungen der älteren Versionen sagen es. Für JDK 14 steht bei derselben Option „By default, this option is disabled.“, für JDK 15 „By default, this option is enabled.“ Die Grenze liegt also zwischen diesen beiden Versionen; ob es die Option vor JDK 14 gab, wurde hier nicht geprüft und wird nicht behauptet.
Der richtige erste Schritt ist also, die Meldung zu lesen und nicht nur den Stacktrace. Und der zweite ist, das Logging daraufhin anzusehen: Ein Logger, der bei Exceptions nur den Typ ausgibt, verschluckt genau die Zeile, die die Arbeit erspart hätte. Das Debuggen von null-Referenzen ist nicht schwieriger geworden, es ist nur an eine Stelle gewandert, an der niemand mehr hinsieht, weil er gelernt hat, dass dort nichts steht.
Es lohnt sich, einmal bewusst eine NullPointerException zu provozieren und die erste Zeile der Ausgabe zu lesen. Dort steht, welche Methode nicht aufgerufen werden konnte, weil welcher Rückgabewert oder welches Feld null war. Wer das einmal gesehen hat, baut keine Debug-Ausgaben mehr, um eine Frage zu beantworten, die die Laufzeit schon beantwortet hat. Und wer es im Log nicht sieht, weiß ab dann, dass das Log die Zeile wegwirft, nicht die JVM.