Skip to main content

So verhindern Sie NullPointerExceptions in Java

Artikel von
Headshot of Lucien Chemaly

Lucien Chemaly

feature java nullpointerexception

21. September 2023

0 Min. Lesezeit

Als erfahrener Java-Entwickler sind Sie wahrscheinlich mit den Feinheiten von Java-Ausnahmen vertraut. Diese Ereignisse weisen auf ungewöhnliche Zustände während der Programmausführung hin. Werden sie nicht richtig behandelt, können sie den normalen Ablauf Ihrer Anwendung stören und zu Abstürzen oder unerwartetem Verhalten führen.

Unter den verschiedenen Java-Ausnahmen sollten Sie besonders auf NullPointerExceptions achten. Sie sind besonders berüchtigt, da sie häufig Laufzeitfehler verursachen und die Stabilität und Zuverlässigkeit Ihrer Anwendung erheblich beeinträchtigen können.

In diesem Artikel erfahren Sie alles über NullPointerExceptions in Java: ihre Ursachen, Folgen und vor allem, wie Sie sie verhindern.

Warum Sie NullPointerExceptions vermeiden sollten

Eine NullPointerException ist ein Laufzeitfehler in Java, der auftritt, wenn versucht wird, auf eine Objektreferenz mit dem Wert null zuzugreifen oder sie zu ändern. Diese null-Referenz bedeutet, dass das betreffende Objekt nicht vorhanden oder nicht instanziiert ist. Findet die Java Virtual Machine (JVM) bei der Bearbeitung eines Objekts eine null-Referenz, löst sie eine NullPointerException aus. Wenn Sie die Ursachen dieser Ausnahmen verstehen und beheben, können Sie stärkere und robustere Anwendungen entwickeln.

NullPointerExceptions können in verschiedenen Situationen auftreten, darunter:

  • Beim Zugriff auf eine Instanzvariable oder beim Aufruf einer Methode eines null-Objekts.

  • Beim Zugriff auf ein Element in einem Array oder einer Collection über eine null-Referenz.

  • Beim Unboxing einer null-Referenz aus einem Wrapper-Objekt in den entsprechenden primitiven Datentyp.

  • Wenn eine null-Referenz als Argument an eine Methode übergeben wird, die diesen Wert nicht erwartet.

Diese Situationen sind in der Regel auf Programmierfehler zurückzuführen, etwa darauf, dass ein Objekt nicht initialisiert, mit null-Werten falsch umgegangen oder vor Objektoperationen nicht ordnungsgemäß auf null-Werte geprüft wurde.

Leider können NullPointerExceptions in einer Entwicklungsumgebung zahlreiche negative Folgen haben, darunter:

  • Abstürze der Anwendung: Tritt eine NullPointerException auf, führt dies häufig zu einem abrupten Ende der Anwendung. Das kann Nutzerinnen und Nutzer frustrieren und zum Verlust nicht gespeicherter Daten führen.

  • Unvorhersehbares Verhalten: NullPointerExceptions können zu unvorhersehbarem Verhalten der Anwendung führen und es erschweren, Probleme zu reproduzieren und zu diagnostizieren.

  • Herausforderungen beim Debugging: Die Ursache einer NullPointerException aufzuspüren, kann zeitaufwendig und kompliziert sein – besonders in großen und komplexen Codebasen.

  • Sicherheitslücken: In manchen Fällen können NullPointerExceptions Sicherheitslücken offenlegen, da böswillige Nutzerinnen und Nutzer sie ausnutzen können, um bestimmte Prüfungen zu umgehen oder sich unbefugten Zugriff auf vertrauliche Daten zu verschaffen.

  • Geringere Produktivität von Entwicklern: Häufige NullPointerException können die Produktivität beeinträchtigen, da Entwickler zusätzliche Zeit für die Behebung und Prüfung dieser Probleme aufwenden müssen.

Wenn Sie die Ursachen, Folgen und Präventionsmethoden für NullPointerExceptions kennen, können Sie ihr Auftreten glücklicherweise verringern und stabilere, effizientere und sicherere Java-Anwendungen entwickeln.

So verhindern Sie NullPointerExceptions in Java

Um NullPointerExceptions in Java zu verhindern, können Entwickler verschiedene Techniken und Best Practices einsetzen, darunter den ternären Operator und die Apache Commons StringUtils-Bibliothek.

Verwenden Sie den ternären Operator

Statt einer if-else-Anweisung können Sie den ternären Operator verwenden, um auf null-Werte zu prüfen und einen Standardwert festzulegen. So verringern Sie das Risiko von NullPointerExceptions. Der ternäre Operator eignet sich für kompakte und gut lesbare bedingte Anweisungen, die null-Werte behandeln und in Java Standardwerte festlegen:

String input = null;
String result = (input != null) ? input : "default";

Dieser Code weist der Variablen result den Wert default zu, wenn die Variable input den Wert null hat. Ist sie nicht null, wird der Wert von input der Variablen result zugewiesen. Der ternäre Operator prüft auf null-Werte und legt einen Standardwert fest.

Verwenden Sie StringUtils aus Apache Commons

Die Apache Commons StringUtils-Bibliothek stellt Hilfsmethoden bereit, mit denen sich null-Werte in Zeichenfolgen sicher behandeln lassen. Das senkt das Risiko einer NullPointerException. So verhindern Sie unerwartete Anwendungsabstürze oder Sicherheitslücken, die durch null-Werte entstehen können:

import org.apache.commons.lang3.StringUtils;
String input = null;
boolean isBlank = StringUtils.isBlank(input); // Returns true if null, empty, or whitespace

Dieser Code verwendet die Klasse StringUtils aus dem Paket org.apache.commons.lang3, um zu prüfen, ob eine Zeichenfolge null oder leer ist oder ausschließlich Leerzeichen enthält. Das Ergebnis wird der booleschen Variablen isBlank zugewiesen.

Mit Snyk können Sie Java-Sicherheitslücken aufspüren und potenzielle Sicherheitsrisiken durch Drittanbieterbibliotheken wie Apache Commons StringUtils minimieren. Für Sicherheit und Stabilität ist es außerdem entscheidend, die neueste Version von Drittanbieterbibliotheken zu verwenden. Prüfen Sie für Apache Commons StringUtils die neueste Version auf der offiziellen Website und aktualisieren Sie sie entsprechend in der pom.xml Ihres Projekts.

Verwenden Sie primitive Datentypen statt Objekten

Verwenden Sie nach Möglichkeit primitive Datentypen statt der entsprechenden Wrapper-Klassen, um potenzielle null-Werte zu vermeiden, die nicht null sein können. Die entsprechenden Wrapper-Klassen können jedoch null sein. Werden sie nicht richtig behandelt, kann dadurch eine NullPointerException auftreten:

int primitiveInt = 0; // A primitive int cannot be null
Integer objectInt = null; // An Integer object can be null, leading to a NullPointerException

Dieser Code deklariert eine Ganzzahlvariable primitiveInt mit dem Wert 0. Da sie einen primitiven Datentyp hat, kann sie nicht null sein. Außerdem wird eine Variable objectInt der Klasse Integer deklariert, die null sein und bei unsachgemäßer Behandlung zu einer NullPointerException führen kann.

Geben Sie in Methoden kein null zurück

Geben Sie in Methoden statt null-Werten ein leeres Objekt zurück:

public List<String> getStrings() {
    return Collections.emptyList(); // Return an empty list instead of null
}

Dieser Code definiert eine Methode getStrings(), die mit der Methode Collections.emptyList() eine leere Liste von Zeichenfolgen zurückgibt, statt einen null-Wert zurückzugeben. Dieser Ansatz ist vorzuziehen, weil er mögliche NullPointerExceptions verhindert und für einen einheitlichen Umgang mit Rückgabewerten sorgt. Dadurch wird der Code leichter lesbar und wartbar. Eine bewährte Möglichkeit, die Rückgabe von null aus Java-Methoden zu verhindern, ist die Verwendung von Optional. Auf dieses Konzept gehen wir später noch genauer ein.

Verwenden Sie .equals() sicher

Rufen Sie .equals() für ein Objekt auf, von dem bekannt ist, dass es nicht null ist, oder verwenden Sie Objects.equals(), um NullPointerExceptions beim Vergleichen von Objekten zu vermeiden:

String str1 = "hello";
String str2 = null;
// Safe usage of .equals()
boolean isEqual = str1.equals(str2); // Returns false, no NullPointerException
// Using Objects.equals()
boolean isEqualObjects = Objects.equals(str1, str2); // Returns false, no NullPointerException

Dieser Code deklariert zwei Zeichenfolgenvariablen: str1 und str2. Er verwendet die Methode .equals() für das Objekt str1, das nicht null ist, und Objects.equals(), um str1 und str2 sicher zu vergleichen. Dabei wird auf null-Werte geprüft, um mögliche NullPointerExceptions beim Objektvergleich zu vermeiden.

Verhindern Sie, dass null als Methodenargument übergeben wird

Verwenden Sie Annotationen wie @NotNull, um anzugeben, dass eine Methode keine null-Argumente akzeptieren soll. So sind Prüfungen zur Kompilierzeit möglich:

public void doSomething(@NotNull String input) {
    // Method implementation
}

Verwenden Sie die Lombok-Annotation @NonNull

Lombok ist eine weit verbreitete Bibliothek, die Java-Code vereinfacht. Die Annotation @NonNull hilft dabei, Parameter auf Nicht-Null-Werte zu beschränken, indem sie geeignete null-Prüfungen generiert:

import lombok.NonNull;
public void doSomething(@NonNull String input) {
    // Method implementation
}

Dieser Code importiert die Annotation @NonNull aus der Lombok-Bibliothek und gibt damit an, dass der Parameter input der Methode doSomething nicht null sein darf. So lassen sich mögliche NullPointerExceptions verhindern, indem null-Werte zur Kompilierzeit geprüft werden.

Verwenden Sie das null-Objekt-Muster

Das null-Objekt-Muster ist ein Entwurfsmuster, das anstelle von null-Werten ein Standardobjekt bereitstellt und so das Risiko von NullPointerExceptions verringert:

public interface Animal {
    void makeSound();
}
public class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}
public class NullAnimal implements Animal {
    @Override
    public void makeSound() {
        // Do nothing
    }
}
public class AnimalFactory {
    public Animal getAnimal(String type) {
        if ("Dog".equalsIgnoreCase(type)) {
            return new Dog();
        }
        return new NullAnimal();
    }
}

Dieser Code definiert ein Animal-Interface und zwei Klassen, die es implementieren: Dog und NullAnimal. Die Klasse AnimalFactory enthält eine Methode namens getAnimal(), die ein String-Argument entgegennimmt und abhängig vom Argument eine Instanz von Dog oder NullAnimal zurückgibt.

Dieser Ansatz eignet sich für den Umgang mit null-Werten, da bei einem ungültigen oder null-Argument für getAnimal() ein Standardobjekt vom Typ NullAnimal erstellt werden kann. So lassen sich mögliche NullPointerExceptions verhindern und ein einheitliches Programmverhalten sicherstellen.

Verwenden Sie die Java Stream API, um null-Werte in Collections zu behandeln

Die Java Stream API stellt Methoden bereit, mit denen sich null-Werte in Collections sicher behandeln lassen:

List<String> strings = Arrays.asList("Hello", null, "World");
strings = strings.stream().filter(Objects::nonNull).collect(Collectors.toList());

Dieser Code verwendet einen Stream, um null-Elemente aus einer Liste von Zeichenfolgen herauszufiltern. So werden mögliche NullPointerExceptions verhindert, die beim Zugriff auf null-Werte in der Liste auftreten können.

Dieser Ansatz vereinfacht die Prüfung auf null und hilft, unerwartete Anwendungsabstürze oder Sicherheitslücken zu verhindern, die durch null-Werte in Collections entstehen können.

Validieren Sie Methodenargumente mit einer speziellen Hilfsmethode

Erstellen Sie eine Hilfsmethode, die Argumente validiert und bei null-Werten eine benutzerdefinierte Ausnahme auslöst. So lässt sich sicherstellen, dass Methodenparameter nicht null sind:

public static <T> T checkNotNull(T reference, String errorMessage) {
    if (reference == null) {
        throw new IllegalArgumentException(errorMessage);
    }
    return reference;
}
public void doSomething(String input) {
    input = checkNotNull(input, "Input should not be null");
    // Method implementation
}

Dieser Code definiert eine Methode checkNotNull, die eine IllegalArgumentException auslöst, wenn der Parameter reference den Wert null hat. Außerdem definiert er eine Methode doSomething, die mit checkNotNull sicherstellt, dass der Parameter input nicht null ist, bevor die Methodenimplementierung fortgesetzt wird.

Dieser Ansatz ist wichtig, um potenzielle NullPointerExceptions zu verhindern: Prüfen Sie zur Laufzeit auf null-Werte und lösen Sie eine Ausnahme aus, wenn null-Werte auftreten.

Verwenden Sie Default-Methoden in Interfaces

Stellen Sie bei der Verwendung von Interfaces Default-Methoden bereit, die mögliche null-Objekte behandeln. So lassen sich NullPointerExceptions vermeiden:

public interface Processor {
    default void process(String input) {
        if (input == null) {
            handleNullInput();
        } else {
            doProcess(input);
        }
    }

    void doProcess(String input);

    default void handleNullInput() {
        // Handle null input or throw a custom exception
    }
}
public class MyProcessor implements Processor {
	@Override
	public void doProcess(String input) {
		System.out.println("Processing input: " + input);
	}
	// optionally override the handleNullInput method
	@Override
	public void handleNullInput() {
		System.out.println("Null input encountered.");
	}
}

Dieser Code definiert ein Interface Processor mit einer Default-Methode process(), die auf null-Eingabewerte prüft und entweder doProcess() oder handleNullInput() aufruft. Die Klasse PMyProcessor implementiert das Interface Processor und überschreibt die Methoden doProcess() und handleNullInput(), um ein benutzerdefiniertes Verhalten festzulegen.

Dieser Ansatz hilft, mögliche NullPointerExceptions zu verhindern, indem er eine einheitliche Behandlung von null-Eingabewerten ermöglicht. So können Sie bei null-Werten ein benutzerdefiniertes Verhalten oder eine Ausnahmebehandlung implementieren.

Verwenden Sie das Builder-Muster

Mit dem Builder-Muster können Sie komplexe Objekte mit optionalen Parametern erstellen, etwa für Farbe, Größe, Form oder andere anpassbare Eigenschaften. Dabei ist sichergestellt, dass erforderliche Parameter stets gesetzt sind und nicht null sein können:

public class Person {
    private final String firstName;
    private final String lastName;

    private Person(Builder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
    }

    public static class Builder {
        private final String firstName;
        private String lastName = "";

        public Builder(String firstName) {
            this.firstName = Objects.requireNonNull(firstName, "First name must not be null");
        }

        public Builder lastName(String lastName) {
            this.lastName = Objects.requireNonNull(lastName, "Last name must not be null");
            return this;
        }

        public Person build() {
            return new Person(this);
        }
    }
}

Dieser Code definiert eine Klasse Person mit einem privaten Konstruktor, der ausschließlich ein Objekt vom Typ Builder entgegennimmt. Dieses enthält zwei Felder: firstName und lastName. Die Klasse Builder stellt sicher, dass das Feld firstName nicht null ist, indem es beim Erstellen des Objekts mit Objects.requireNonNull() geprüft wird. Das Feld lastName darf null sein, muss jedoch mit der Methode lastName() geprüft und festgelegt werden.

Dieser Ansatz hilft, mögliche NullPointerExceptions zu verhindern, indem alle erforderlichen Felder beim Erstellen des Objekts initialisiert und auf null-Werte geprüft werden. Das sorgt für mehr Einheitlichkeit und Zuverlässigkeit im Programm.

Verwenden Sie das Double-Checked-Locking-Muster mit dem singleton-Muster

Beim Erstellen von singletons kann das Double-Checked-Locking-Muster dafür sorgen, dass die singleton-Instanz niemals null ist:

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Dieser Code definiert eine Singleton-Klasse, die sicherstellt, dass zur Laufzeit nur eine Instanz der Klasse erstellt wird. Double-Checked Locking verhindert dabei potenzielle Race Conditions und NullPointerExceptions.

Dieser Ansatz ist wichtig, um NullPointerExceptions zu verhindern: Er stellt sicher, dass die Instanz der Singleton-Klasse nur einmal erstellt wird und nachfolgende Aufrufe der Methode getInstance() dieselbe Instanz zurückgeben. So wird verhindert, dass der Variablen instance versehentlich null-Werte zugewiesen werden und potenzielle NullPointerExceptions verursachen.

Verwenden Sie die Optional-Klasse

Die Optional-Klasse ist ein leistungsstarkes Werkzeug, um NullPointerExceptions zu vermeiden, indem potenzielle null-Werte explizit behandelt werden:

import java.util.Optional;
public class OptionalExample {
    private String someString;
    public OptionalExample(String someString) {
        this.someString = someString;
    }
    public Optional<String> getString() {
        return Optional.ofNullable(someString);
    }
}
//Usage
OptionalExample example1 = new OptionalExample("Hello, World!");
Optional<String> optString1 = example1.getString();
String value1 = optString1.orElse("default");
System.out.println(value1); // Output: Hello, World!
OptionalExample example2 = new OptionalExample(null);
Optional<String> optString2 = example2.getString();
String value2 = optString2.orElse("default");
System.out.println(value2); // Output: default

Dieser Code verwendet die Klasse Optional, um null-Werte zu behandeln. Er gibt ausdrücklich an, dass ein Wert fehlen kann, und verwendet orElse(), um einen Standardwert bereitzustellen und so potenzielle NullPointerExceptions zu verhindern. Rufen Sie get() nicht direkt für Optional auf. Verwenden Sie stattdessen Methoden wie orElse(), um einen Standardwert bereitzustellen, wenn das Optional leer ist. So lässt sich eine NoSuchElementException verhindern.

Dieser Ansatz macht nicht nur deutlich, dass ein Wert vorhanden sein kann oder auch nicht, sondern sorgt auch für die korrekte Behandlung dieser Fälle. So lassen sich potenzielle Fehler wie NullPointerException oder NoSuchElementException vermeiden.

Testen Sie früh und regelmäßig

Schreiben Sie umfassende Unit-Tests, um NullPointerExceptions während der Entwicklung zu erkennen, bevor sie in der Produktion Probleme verursachen:

@Test
public void testGetStrings() {
    MyClass obj = new MyClass();
    List<String> result = obj.getStrings();
    assertNotNull(result); // Ensure the result is not null
}

Dieser Code definiert einen JUnit-Testfall für die Methode getStrings() der Klasse MyClass. Anschließend erstellt er eine Instanz von MyClass, ruft die Methode getStrings() auf und prüft mit der Methode assertNotNull(), ob das Ergebnis nicht null ist.

Dieser Ansatz hilft, potenzielle NullPointerExceptions zu verhindern, indem geprüft wird, ob das Ergebnis der Methode getStrings() not null ist, bevor weitere Operationen damit ausgeführt werden. Indem sichergestellt wird, dass das Ergebnis not null ist, kann der Testfall potenzielle Probleme mit null-Werten erkennen und unerwartete Anwendungsabstürze oder Sicherheitslücken verhindern.

Fazit zu NullPointer

Das Verhindern von NullPointerExceptions in Java-Anwendungen ist entscheidend für robuste, zuverlässige und sichere Software. Ab Java 14 wurden Verbesserungen bei der Behandlung von NullPointerExceptions eingeführt, die das Verhindern und die Fehlerbehebung erleichtern. Für das Debugging kann es sich daher lohnen, auf neuere Java-Versionen zu aktualisieren.

Mit einer Kombination verschiedener Techniken – etwa dem ternären Operator, der Validierung von Methodenargumenten und Designmustern wie Builder- und Null-Object-Mustern – können Sie das Risiko von NullPointerExceptions wirksam senken. Diese Praktiken tragen nicht nur zur Stabilität der Anwendung bei, sondern verbessern auch die gesamte Entwicklungserfahrung.

Snyk ist ein unverzichtbares Tool für Entwickler, die sichere und zuverlässige Anwendungen erstellen möchten. Mit Snyk können Sie auf eine Reihe von Tools und Services zugreifen, die Sicherheitslücken in Ihrem Code erkennen und beheben. Snyk lässt sich außerdem in beliebte Entwicklungsumgebungen und Plattformen integrieren, sodass Sie Sicherheit ganz einfach in Ihren Entwicklungsprozess einbinden können.

Wenn Sie Ihre Java-Entwicklung auf die nächste Stufe heben und zu einem sicheren und stabilen Software-Ökosystem beitragen möchten, sollten Sie leistungsstarke Snyk-Tools wie die IDE-Plugins, die CLI und die kostenlosen, praxisorientierten Java-Sicherheitsschulungen von Snyk nutzen. Mit diesen Tools können Sie Sicherheitslücken wie NullPointerExceptions erkennen und melden und so die Sicherheit und Zuverlässigkeit Ihrer Anwendungen gewährleisten.

Java-Schwachstellen finden und beheben

Schützen Sie Ihre Anwendungen mit der Schwachstellensuche und den Behebungsempfehlungen von Snyk. 

Keine Kreditkarte erforderlich.

Oder registrieren Sie sich mit Azure AD Docker ID Bitbucket

Mit der Nutzung von Snyk erklären Sie sich mit unseren Richtlinien einverstanden, einschließlich unserer Nutzungsbedingungen und Datenschutzrichtlinie.


Gepostet in:

Weiterlesen

blog feature snyk container custom base recommendations
Blog

Evo Agentic AppSec: Sicherheit, die mit der KI-Entwicklung Schritt hält

KI-gestützte Entwicklung überholt herkömmliche Security-Workflows. Snyk Agentic AppSec setzt Security-Agenten ein, die Schwachstellen kontinuierlich finden, beheben und überprüfen – während Menschen die Maßstäbe festlegen.

feature insights context
Blog

Autonome Angriffe sind bereits Realität. Die Verteidigung muss Schritt halten.

Autonome Angreifer verkürzen das Zeitfenster für die Verteidigung. Erfahren Sie, wie kontinuierliches Erkennen, Beheben, Validieren und Verhindern Sicherheitsteams helfen kann, Schritt zu halten.

illustration hero ai
Blog

Der KI-Hurrikan ist da

KI beschleunigt gleichermaßen die Softwareentwicklung und Cyberangriffe. Führungskräfte müssen Agents und Code von Anfang an absichern, zur Laufzeit Kontrollen durchsetzen und Abwehrmaßnahmen unabhängig validieren.