null and NullPointerException in Java: How to Avoid It

Introduction NullPointerException is the most common exception in Java programs. Its inventor, Tony Hoare, famously called the null reference his "billion-dollar mistake". This tutorial explains null and NullPointerException in Java, and how to avoid it. We will start with what null means, and where it comes from. We will then see every way a NullPointerException can be thrown - including some surprising ones - and read the helpful messages Java gives. Next we will go through the techniques that prevent it: checks, safe defaults, failing fast, Optional, and design choices that keep null out of your code in the first place.

(Note - This tutorial builds on Stack vs Heap Memory and Object ReferencesClick to read tutorial: Stack vs Heap Memory in Java and Object References.) What is null? null is a special value which means "this reference points to no object". It can be assigned to any reference type - String, arrays, your own classes - but not to a primitive such as int. null appears in a program in several ways -
  • fields of reference types, and elements of new object arrays, default to null;
  • code assigns it explicitly, often to mean "no value" or "not found";
  • library methods return it - Map.get() for a missing key, System.getenv() for an unset variable, BufferedReader.readLine() at the end of input.
Operations which do not use the object are fine with null: comparing with ==, assigning, passing it as an argument, and joining it to a string, which adds the text null. When NullPointerException is thrown A NullPointerException - an unchecked exception (see checked vs uncheckedClick to read tutorial: Checked vs Unchecked Exceptions in Java) - is thrown whenever the program tries to use the object a null reference does not point to. Since Java 14, its message says exactly what was null - see Helpful NullPointerExceptions in Java 14Click to read tutorial on Java 14 helpful NullPointerExceptions.
Ways a NullPointerException can happen
//NpeSources.java
import java.util.HashMap;
import java.util.Map;

public class NpeSources {
  static class Customer {
    String name;
    Customer referredBy;
  }

  static String[] labels = {"call a method", "read a field", "array length", "array element",
      "unboxing", "switch on null", "chained call", "throw null"};

  public static void main(String[] args) {
    String text = null;
    Customer customer = new Customer();               // customer.name and referredBy are null
    int[] numbers = null;
    Map<String, Integer> stock = new HashMap<>();
    for (int i = 0; i < labels.length; i++) {
      try {
        switch (i) {
          case 0 -> text.length();
          case 1 -> System.out.println(customer.referredBy.name);
          case 2 -> System.out.println(numbers.length);
          case 3 -> numbers[0] = 1;
          case 4 -> { int count = stock.get("pens"); }        // Integer null unboxed to int
          case 5 -> { switch (text) { case "a" -> System.out.println("a"); default -> System.out.println("?"); } }
          case 6 -> customer.name.trim().length();
          case 7 -> { RuntimeException e = null; throw e; }
        }
      } catch (NullPointerException e) {
        System.out.printf("%-15s -> %s%n", labels[i], e.getMessage());
      }
    }
  }
}
 OUTPUT of the above code
call a method   -> Cannot invoke "String.length()" because "text" is null
read a field    -> Cannot read field "name" because "customer.referredBy" is null
array length    -> Cannot read the array length because "numbers" is null
array element   -> Cannot store to int array because "numbers" is null
unboxing        -> Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null
switch on null  -> Cannot invoke "String.hashCode()" because "<local6>" is null
chained call    -> Cannot invoke "String.trim()" because "customer.name" is null
throw null      -> Cannot throw exception because "e" is null
Explanation of the code
  • The first four are the obvious cases: calling a method, reading a field, or using the length or an element of an array through a null reference.
  • Unboxing is a hidden one: stock.get("pens") returned null for the missing key, and assigning it to an int called intValue() on it.
  • A classic switch on a null String or enum throws too, unless it has a case null (Java 21+). Its message is less helpful - it names <local6>, a hidden variable the compiler creates to hold the switch value, and hashCode(), which the compiled switch calls to find the matching case.
  • In a chain such as customer.name.trim().length(), the message pinpoints which link was null - here customer.name.
Technique 1 - check for null, in the right order When a value may legitimately be absent, check it before using it. Put the null check first in a condition, so that short-circuit evaluationClick to read tutorial: Java Arithmetic, Relational and Logical Operators skips the risky call. When comparing with a constant, call equals() on the constant, which can never be null.
Checking for null safely
//NullChecks.java
import java.util.Objects;

public class NullChecks {
  static boolean isAdmin(String role) {
    return "ADMIN".equals(role);                  // safe even when role is null
  }

  static int nameLength(String name) {
    if (name != null && !name.isBlank()) {        // null check first
      return name.strip().length();
    }
    return 0;
  }

  public static void main(String[] args) {
    System.out.println("isAdmin(null)       = " + isAdmin(null));
    System.out.println("nameLength(null)    = " + nameLength(null));
    System.out.println("nameLength(\" Tom \") = " + nameLength(" Tom "));
    System.out.println("Objects.equals(null, null) = " + Objects.equals(null, null));
    System.out.println("Objects.toString(null, \"-\") = " + Objects.toString(null, "-"));
    System.out.println("Objects.hash(null, 1) = " + Objects.hash(null, 1));
  }
}
 OUTPUT of the above code
isAdmin(null)       = false
nameLength(null)    = 0
nameLength(" Tom ") = 3
Objects.equals(null, null) = true
Objects.toString(null, "-") = -
Objects.hash(null, 1) = 962
The java.util.Objects class has null-safe helpers: equals(), toString(value, default), hash() and isNull()/nonNull(). Technique 2 - replace null with a safe default Often the simplest fix is to never let null into the rest of the code: return an empty string, an empty collection, or a sensible default instead.
Safe defaults instead of null
//SafeDefaults.java
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class SafeDefaults {
  static List<String> findOrders(String customer) {
    Map<String, List<String>> orders = Map.of("Tom", List.of("#101", "#105"));
    return orders.getOrDefault(customer, List.of());      // empty list, never null
  }

  public static void main(String[] args) {
    for (String c : new String[] {"Tom", "Nancy"}) {
      List<String> list = findOrders(c);
      System.out.println(c + " has " + list.size() + " orders " + list);   // no null check needed
    }
    String nickname = null;
    String shown = Objects.requireNonNullElse(nickname, "guest");
    System.out.println("Hello, " + shown);
  }
}
 OUTPUT of the above code
Tom has 2 orders [#101, #105]
Nancy has 0 orders []
Hello, guest
Never return null instead of an empty collection or array. Callers can loop over an empty list without any check; a null list forces every caller to remember one. Map.getOrDefault() and Objects.requireNonNullElse() make defaults easy.
Technique 3 - fail fast with requireNonNull When null is not allowed, reject it at once - in the constructor or at the start of the method - with Objects.requireNonNull(). Otherwise the null is stored, and the exception happens much later, far from the code which caused it. (See exception handling best practicesClick to read tutorial: Java Exception Handling Best Practices.)
Rejecting null in a constructor
//FailFastNull.java
import java.util.Objects;

public class FailFastNull {
  static class Invoice {
    private final String customer;
    Invoice(String customer) {
      this.customer = Objects.requireNonNull(customer, "customer must not be null");
    }
    String title() { return "Invoice for " + customer.toUpperCase(); }
  }

  public static void main(String[] args) {
    System.out.println(new Invoice("Tom").title());
    try {
      new Invoice(null);
    } catch (NullPointerException e) {
      System.out.println("Rejected at creation: " + e.getMessage());
    }
  }
}
 OUTPUT of the above code
Invoice for TOM
Rejected at creation: customer must not be null
Technique 4 - Optional for values that may be absent For a method whose result may legitimately be missing, return Optional<T> instead of a value-or-null. The return type then tells callers that "no result" is possible, and gives them methods to deal with it.
Optional instead of null for a missing result
//OptionalDemo.java
import java.util.List;
import java.util.Optional;

public class OptionalDemo {
  static Optional<String> findEmail(String name) {
    return List.of("tom@javabrahman.com", "harry@javabrahman.com").stream()
        .filter(e -> e.startsWith(name.toLowerCase() + "@"))
        .findFirst();
  }

  public static void main(String[] args) {
    System.out.println(findEmail("Tom").orElse("no email"));
    System.out.println(findEmail("Nancy").orElse("no email"));
    findEmail("Harry").ifPresent(e -> System.out.println("Sending newsletter to " + e));
    System.out.println(findEmail("Nancy").map(String::length).orElse(0));
  }
}
 OUTPUT of the above code
tom@javabrahman.com
no email
Sending newsletter to harry@javabrahman.com
0
Use Optional mainly as a return type - not for fields or parameters. The Java 8 Optional Basics tutorialClick to read the Java 8 Optional Basics tutorial covers it in depth. Design choices that keep null out
  • Initialize fields when they are declared or in the constructor - an empty list, an empty string or a default object - so they are never null.
  • Make objects immutable and validate everything in the constructor; a valid object then stays valid.
  • Document null - say in Javadoc whether a parameter may be null and whether a method may return it. Many teams use @Nullable and @NonNull annotations, which IDEs and tools check.
  • Do not catch NullPointerException to hide it - it is a bug signal. Fix the code that produced the null.
Summary In this tutorial we first saw that null means a reference points to no object, and where it comes from - default values, explicit assignments and library methods. We then triggered a NullPointerException in eight ways, including unboxing and switching on null, and read Java's helpful messages. Next we prevented it with null checks in the right order and "constant".equals(), with safe defaults such as empty collections, by failing fast with requireNonNull(), and with Optional return types. Lastly, we went through design choices that keep null out of code altogether. In the next tutorial, we move on to file I/O with reading and writing files with Path and FilesClick to read tutorial: Reading and Writing Files in Java with Path and Files.