Sir Tony Hoare called the invention of the null reference (something he created) his 'billion-dollar mistake'. In Java we have become so accustomed to handling some error conditions by returning null
that it is second nature to null check everything, but in many cases there are better options, rather than returning null
. Refer to the table below for some common examples:
Return Type | Non-null Return Value |
---|---|
String | "" (An empty string) |
List / Set Map / Iterator | Use the Collections class, e.g. Collections.emptyList() |
Stream | Stream.empty() |
Array | Return an empty, zero-length array |
All other types | Consider using Optional (but read the Optional section below first) |
In guaranteeing to return non-null values to callers of our API, our users can opt to not include the noisiness of the null check in their code base. It is important however, that should this approach be taken, that we ensure that it is applied consistently across an entire API. It is very easy to erode trust in an API if it fails to consistently apply patterns (and in failing to do so, causes the user to encounter unexpected null pointer exceptions).
Understand when to use Optional
Java 8 introduced Optional
as a way of lessening the possibility of null pointer exceptions, because when a method returns Optional
, it guarantees that it will be non-null. It is then up to the consumer of the API to determine if the returned Optional
contains an element, or is empty. In other words, an Optional<T>
can be thought of as a container for at most one element.
The Optional
return type is best used in select cases when:
- A result might not be able to be returned, and
- The API consumer has to perform some different action in this case
Optional provides a number of convenience methods, some of which are highlighted below, in a hypothetical situation where there exists a public Optional<Car> getFastest(List<Car> cars)
method:
// getFastest returns Optional<Car>, but if the cars list is empty, it
// returns Optional.empty(). In this case, we can choose to map this to an
// invalid value.
Car fastestCar = getFastest(cars).orElse(Car.INVALID);
// If the orElse case is expensive to calculate, we can also use a Supplier
// to only generate the alternate value if the Optional is empty
Car fastestCar = getFastest(cars).orElseGet(() -> searchTheWeb());
// We could alternatively throw an exception
Car fastestCar = getFastest(cars).orElseThrow(MissingCarsException::new);
// We can also provide a lambda expression to operate on the value, if it
// is not empty
getFastest(cars).ifPresent(this::raceCar)
The examples above all demonstrate good times for an API to return Optional
. On the other hand, if most users of an API that returns Optional
write code as shown below, it is possible to argue that this is an object-oriented version of a null-reference check, and is probably no better (or any more intuitive) than simply declaring that this particular API will return null
rather than Optional
.
// Whilst it is ok to call get() directly on an Optional, you risk a
// NoSuchElementException if it is empty. You can wrap it with an
// isPresent() call as shown below, but if your API is commonly used like
// this, it suggests that Optional might not be the right return type
Optional<Car> result = getFastest(cars);
if (result.isPresent()) {
result.get().startCarRace();
}
There are two final rules when it comes to returning an Optional
in API:
- Never return
Optional<Collection<T>>
from a method, as this can be more succinctly represented by simply returningCollection<T>
with an empty collection (as mentioned earlier in this refcard). - Never, ever return
null
from a method that has a return type ofOptional
!