The famous computer scientist Sir Tony Hoare is often credited for the invention of null, and by extension the bane of Java software developers, the NullPointerException (NPE). He calls it his “Billion Dollar Mistake.”
But is null really a mistake? NPEs are really just a case of a variable in an unusable state, and that’s not Tony Hoare’s fault. Before NPE, those with long memories will recall an error called a S0C7 (pronounced “sock seven”) that plagued programmers on early IBM platforms just as much. Other languages call null by different names (nil, for instance). But they represent the same root value — nothing.
That said, Tony Hoare is correct that null has become a language element that many programmers feel is more “bug” than “feature.” In this post, we’ll examine why that is, and look at some of the ways languages have evolved to address it.
The Problem of Unusable State
Let’s imagine a simple customer database. And let’s say your database saves a customer’s name, address, and credit card number. What do you do for customers who decline to have their credit card information saved? What do you do for customers who pay cash in a brick and mortar outlet? Will you refuse their business because your database has a credit card field that must be populated?
Of course not. There is, and always has been, a need to represent a lack of data. You could, for example, populate your credit card field with some obviously un-credit-card-like value, say all zeroes or all nines. But that is just null by another name. You’d still need to code for the all-zeroes or all-nines special cases. True, you may not experience a crash. But if you aren’t careful, your special value could flow downstream and cause problems later on.
My choice to use a database for this example is not an accident. Null has been part of SQL design since the very beginning. The creator of the relational database, E.F. Cobb, made support for null one of his rules for defining what a relational database is. He felt there are just times when a data value may not be available.
So maybe null — or something like null — is unavoidable. However, when using a value like null, there is always the danger that you will miss an explicit null check. This has led to countless exceptions and crashes, thus earning null its bad reputation. Is there anything we can do to help contain and minimize null values?
The Null Object Pattern
One way is to use the Null Object pattern.
In this pattern, to represent the null case for a given class, a special subclass is created. The trick is that all of its methods will be no-op (i.e. they do nothing, that is, “no operation”). An instance of this subclass can be used anywhere an instance of the parent class can go, and it’s safe, because it doesn’t do anything if called.
You can find a brief discussion of this approach in “Uncle Bob” Martin’s book, Agile Software Development: Principles, Patterns and Practices. He recommends creating a static variable in each potentially nullable class that implements, as he calls it, “nothing” — although his definition of “nothing” isn’t completely pure. If a method returns a value, for example, then you have to implement something.
In his book, Refactoring to Patterns, Joshua Kerievsky recommends the Null Object pattern, but also points out that it is probably better to implement a shared interface, rather than subclassing. Otherwise, it can create a maintenance risk. When adding functionality to a class with a null sub-class, you’d need to make certain that the null version properly implements (or doesn’t implement) the new interface elements or risk having the base class methods unintentionally executed by your null version.
The Special Case Pattern
In his book, Refactoring (and in Patterns of Enterprise Architecture), Martin Fowler suggested a variation on the Null Object Pattern. He calls it the “Special Case Pattern.” In this case, he suggests the creation of null objects that, instead of having all no-op methods, implement a meaningful default behavior. For instance, a database of utility customers might return a customer name value of “Occupant” for an address whose owner has not yet been identified. However, as Fowler points out, this pattern requires that a suitable default behavior exists.
Syntactic Approaches To Null Safety
Some languages try to provide null-safety syntactically. The safe call operator, for instance, represented by a question mark in languages like Kotlin, allows for the referencing of methods on null objects without throwing errors. For instance:
Instead of throwing an NPE, this syntax returns null if somethingNullable is null. It acts as if somethingNullable was a null object with a no-op implementation of someMethod.
The“?:” or “Elvis” operator in kotlin is another example. For instance:
evaluates to an appropriate value of the correct type if somethingNullable is null.
Maybe another Option?
A more comprehensive approach to dealing with the “no value” condition is to use the Option Pattern, also called Optional or Maybe. An Optional is a container for a value that may or may not be present. It can be seen as a collection having one or zero elements.
It’s not perfect and doesn’t prevent all NPE’s. However, the intention is to encourage you to account for nulls in a safe way. You have to go through the Optional in order to get to the nullable object inside. It includes methods that help you deal with cases where a value is or isn’t present. Classes for implementing the Option Pattern are available in languages like Scala, and an Optional class was introduced as part of the standard library in Java 8.
Optionals in Java 8
To understand Optionals better, let’s look at some examples of how Optionals work in Java 8. To start, let’s imagine we have a simple Person class with a name, age, and occupation. As coded, any of the variables in this class can be null and an object of type Person can, itself, be null. So this offers lots of opportunities to explore null safety.
Now let’s look at Optional object creation to see what variations are available. In Java, an Optional is a class that wraps a value, which may or may not be null, and then offers methods for interacting with the contents in a safe way. You have to go through the Optional to access the object inside.
There are a number of ways to create an Optional in Java 8.
Let’s start with the first. To create an empty Optional for our Person class, i.e., one which contains nothing (analogous to a null object), use Optional.empty().
From here, we can query our Person Optional to determine the state of the wrapped object. So, for instance, you have the boolean isPresent()
method, which will return false if the object is empty as in the example above: optionalPerson.isPresent() == false
.
To populate the object inside, you have a choice between two methods, depending on whether or not you know the object isn’t null. If you’re sure it definitely isn’t null, use Optional.of
as follows:
If you try to pass null instead of a valid Person, it will immediately throw an NPE.
If you have an object that may or may not be null, use Optional.ofNullable
as follows:
In this case, the value of person will be empty since bob is null. It would be the same as writing:
And now you have all of the null-safe features of the Optional at your disposal.
So, for instance, without Optionals, you might want to check for null before printing like this:
But using a Java 8 Optional, the same code can be written as:
The ifPresent()
method works in a similar way to the null safe operator. But instead of conditionally invoking a method, it takes a function as an argument that gets invoked only if the Optional is not empty. So in this case, the person’s name will only get printed if the Person object inside the Optional is not null. By forcing you to go through the Optional to run the println
method, the compiler enforces null safety and accidental null references are avoided.
Java 8 Optionals also offer a number of methods for safely accessing the nullable object inside, the orElse()
or orElseGet()
methods. The difference between the two is subtle. The orElse()
method expects a default value as an argument, while orElseGet()
expects a function that returns a default value. In the example below, resultString
will be assigned “Default String” if optionalString
contains a null object.
The orElseGet()
version would look like this:
A variation on the orElse
theme is orElseThrow()
. Instead of returning a default value, it would throw an appropriate exception when the object inside the Optional is null. So the example above might look like this:
Lastly, Java 8 Optionals offer another way to access the wrapped object. The get()
method will either return the object or throw a NoSuchElementException
error if it is empty. So you’d need to first check that the object isPresent()
before calling get()
- which is little better than explicitly checking for null. So better to stick with the other methods.
You can use filtering to perform inline tests on an Optional. It’s similar to the way a filter
acts on a stream — except imagine it is a stream of just one value or none. The filter
takes a predicate as an argument and always returns an Optional. If the object wrapped by the Optional is not empty and passes the predicate test, then it is returned as is. But if it is either empty or fails to pass the test, then filter
returns an empty Optional. So, for instance, if you wanted to determine if a Person wrapped by an Optional was of a certain age, you could write:
We can also perform transformation operations on Optionals using map
. The map
applies a function to the object wrapped inside an Optional and then returns the result wrapped in an Optional. So, for instance, we can combine a map
with a filter
to determine if a Person is a computer programmer.
But wait. What if our Person is unemployed and has no occupation? In other words, what if the occupation is null? In the real world, you have to assume that objects may be nullable and the methods of a non-null object may return null. Null safety is needed at every level. To cover that case, you might want the occupation getter method to return an Optional like this:
But now our mapping and filtering won’t work. Map will want to return a nested Optional with an Optional <String>
inside! You’d get Optional <Optional<String>>
. So instead of map
, we’ll use flatmap
. The flatmap
“flattens” the Optional hierarchy created by our getOccupation
method. Instead of returning an Optional within an Optional, it just returns the simple Optional we are looking for. So if we were to write the occupation getter as above, returning an Optional, our computer programmer method would look like this:
Conclusion
Null has been around since long before Java and other modern programming languages, And it is unlikely we can live without some representation for missing data or “no data”. But there are ways to deal with these situations that don’t have to throw unexpected NPE errors. There are design patterns like Null Object and Special Case as well as more recent language-based solutions like Optionals that can help manage the problem. Used conscientiously, they can provide a large degree of null safety and generally promote good design practices and better APIs.