Stop Returning Null in Java

I call it my billion-dollar mistake.

At that time, I was designing the first comprehensive type system for references in an object-oriented language.

My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement.

This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
— Tony Hoare, inventor of null reference

Overview

In this article, we'll explain why using null to represent the absence of a return value is a bad approach.   We'll propose some better alternatives to make our code cleaner and less error-prone.


Pitfalls of Null

In Java, a null value can be assigned to an object reference of any type to indicate that it points to nothing.

Calling a method on a null reference will throw a NullPointerException:
Blog blog = null;
long id = blog.getId(); // NullPointerException

The compiler assigns null to any uninitialized static and instance members of reference type.  

To illustrate this point, let's take a look at a class:

public class Blog {
  private long id; // 0
  private String name; // null
  private List<Article> articles; // null
 
  public long getId() {
    return id;
  }
   
  public String getName() {
    return name;
  }
  
  public List<Article> getArticles() {
    return articles;
  }
}
In the absence of a constructor, the getArticles() and getName() methods will return a null reference.

Writing methods that return null forces the calling code to be littered with guard clauses like this:

if (blog.getName() != null) {
  int nameLength = blog.getName().count();
}

if (blog.getArticles() != null) {
  int articleCount = blog.getArticles().size();
}

This is annoying, difficult to read, and susceptible to run-time exceptions if anyone misses a null check anywhere


Return an Empty Collection

If the method returns a collection type, then we can simply substitute an empty collection.

Java 5+

The Collections class has a static method for this purpose:

return Collections.emptyList();

Java 9

Java 9 introduced a new factory method for this:

return List.of();

Both of these return an immutable list so the calling code should make no attempt to modify it.


Return an Optional object

Java 8 introduced a class called java.util.Optional that's designed to address the pain points associated with null values.  

An Optional is a container that can either be empty or contain a non-null value.

Brian Goetz, Java Language Architect at Oracle, explained the reasoning behind adding its inclusion:

Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result”, and using null for such was overwhelmingly likely to cause errors.

We can use Optional.empty() to represent empty container and Optional.of(value) to represent an actual value.

public Optional<Blog> getBlog(long id) {
  // Blog not found
  if (!found) {
    return Optional.empty(); 
  }
  
  // Blog found
  return Optional.of(blog);
}

We can use the ifPresent() method to retrieve the value if it exists:

Optional blog = getBlog(100);
blog.ifPresent(System.out::println);

The null check has been abstracted away and enforced by the type system. If the Optional object were empty above, then nothing would be printed.


Return a Null Object

The Null Object Pattern is described in the Gang of Four’s Design Patterns book.  The intent of the pattern is to identify behavior that should occur when a null is encountered and encapsulate it using a static constant.

Let's expand on the previous Blog class example:

public class Blog {
  public static Blog NOT_FOUND = new Blog(0, "Blog Not Found", Collections.emptyList());
  
  private long id;
  private String name;
  private List<Article> articles;
 
  public Blog (long id, String name, List<Article> articles) {
    this.id = id;
    this.name = name;
    this.articles = articles;
  }
  
  public long getId() {
    return id;
  }
 
  public List<Article> getArticles() {
    return articles;
  }
}

On line 2, we've declared a constant to represent our null object.   It's a Blog object with some sensible values for a non-existent blog.

Any methods that return a Blog type can now use Blog.NOT_FOUND instead of null. 

Throw an Exception

Exceptions should be reserved for exceptional conditions.    If we always expect to find a value then throwing an exception makes sense.

For example, we could throw an unchecked exception if an ID passed to our method isn't found in the database.

throw new IllegalArgumentException("Invalid Blog ID");

The decision to use an exception when there is nothing to return is highly dependent on the nature of the application.


How JDK Methods Handle Missing Values

The get() method in java.util.HashMap returns null in two situations:
  • Key does not exist
  • Key exists and the value is null
Map<Integer, String> map = new HashMap<>();
map.put(1, "Amir");
map.put(2, "Beth");
map.put(3, null);

String name = (String)map.get(3);  // null
name = (String)map.get(4);  // null

This leads to ambiguity because we can't be certain what null actually means here.   We would need an additional check with the containsKey() method to be sure.

In contrast, the get() method in java.util.ArrayList throws an IndexOutofBoundsException for an invalid index.
List<String> list = new ArrayList<>();
list.add("hello");
list.get(1); // IndexOutofBounds Exception