Null is your friend, not a mistake
I’ve been programming in Java for a long, long time. I’ve learned what it takes to write and maintain big (as in million-lines of code) software in Java and I’ve witnessed industry-wide struggle to avoid and contain the dreaded NullPointerException
(NPE) that seemed to plague any reasonably-sized Java codebase. This realization of the danger of the null
reference had dawned on the industry way before its inventor, Tony Hoare, had admitted in 2009 that null reference was his “Billion Dollar Mistake”.
It was not that obvious back in 1996 when Java 1.0 was released. Let us take a look at just one, now famous, example of a typical Java API from that era: File.list()
method. It can be used to list contents of a directory like this:
for (String name : new File("directory").list()) {
System.out.println(name);
}
It works only if the directory exists. Otherwise it throws an NPE, because list
returns null
. But who would ever write code like that? Not only list
documentation clearly states that it returns null
when directory is missing, but modern IDEs flag this particular code with a warning right away.
However, people do these kind of mistakes all the time when programming in Java. By now there is a big body of research showing how it happens. It turns out that most of the time our API functions are not supposed and are not expected by other developers to return null. In corner cases, like the absence of something, it is a convention in Java to return some “null object” (empty collection, unfilled domain object, etc) or, as a lesser evil than returning null, to throw an exception. That is how Files.newDirectoryStream
— a modern version of File.list
, is designed — no nulls anywhere.
So when null does appear as a result of a function in some special case, as performance optimization, uninitialized reference field, etc, it often catches other code off-guard, unprepared to deal with it. Not only it is rare to be having to deal with nulls, but the code you have to write for dealing with nulls in Java is long and verbose:
String[] list = new File("directory").list();
if (list != null) {
for (String name : list) {
System.out.println(name);
}
}
No wonder you’d rather choose not to write it, unless absolutely necessarily (necessity often happens when your customer discovers NPE in production).
Fear of null leads to some extreme practices. There are Java coding styles that forbid null completely, forcing heinous workarounds upon developers. Have you seen a codebase where every domain object implements a special Null
interface and must provide manually coded “null object” instance? Lucky if you have not, but I bet you’ve seen Optional<T>
wrappers that pollute Java code only for the sake of avoiding nulls.
There are collection-like APIs that forbid null elements out of extra caution and there are members of core Java team that consider support for nulls in Java collection framework to be a mistake. This is extremely sad.
The truth is that the concept of null is not mistake, but Java’s type-system, that considers null to be a member of every type, is. See, "abc"
is a valid String
in Java and null
is a valid String
, too, yet you can use all the string methods like substring
on the former, but attempts to use them on the latter universally fail at runtime. Is it type-safe? Not really. It is normal when some operations fail at runtime on certain values of a type (like division by zero), but when a value leads to runtime failure of all operations, it shows that the value should not belong to this type in the first place. All those NPEs in Java programs indicate an obvious flaw in Java type-system.
More type-safe programming languages, like Kotlin, fix this flaw by properly incorporating the concept of null into the type system. Adding inspections and warning does help, too, but it is not enough. Clearly, a sound type system must only allow such values of type String
that can support its operations, so in Kotlin putting null
into a variable of type String
is not just a warning, but a type error akin to assigning an integer value 42
to a variable of type String
.
Proper null support in type system is a game-changer for API design. There is no reason whatsoever to fear null anymore. Having some functions that return a nullable type String?
side-by-side with others that return non-null String
is as normal as having some functions that return String
side-by-side with others that return Int
. They are simply different types with different and safe sets of operations available for them.
Type-safe null is better, more efficient, and less verbose alternative to represent all sorts of “missing results”. Look at Kotlin standard library for inspiration, for example. There is String.toIntOrNull()
function that parses a string to an integer, if possible, or returns null if not. It is joy to use. Writing a command-line application that takes an integer parameter and properly complains on its absence is easy:
fun main(args: Array<String>) {
val id = args.getOrNull(0)?.toIntOrNull()
?: error("id expected")
// ...
}
Embrace null in your API design. Null is your friend with Kotlin. There is no reason to fear it and no reason to work around it with “null object” pattern or wrappers, let alone with exceptions. Proper use of nulls in your APIs results in more readable and safer code, free from boilerplate.
Further reading
If you liked the topic and want to know more details from the standpoint of language design then consider reading the companion to this story— “Dealing with absence of value”.