Overview Link to heading

This blog article explores the keyword null, a staple in every programmer’s daily work. Should we reconsider the use of null references in our software? Could encountering null in our code actually be harmful? This article aims to address these questions and provide insight into the implications of using null.

Robert Oppenheimer’s regret over inventing the atomic bomb serves as a fitting analogy for Tony Hoare’s regret over introducing the null reference (Image-Source: Movie: Oppenheimer (2023) by Christopher Nolan).

Robert Oppenheimer’s regret over inventing the atomic bomb serves as a fitting analogy for Tony Hoare’s regret over introducing the null reference (Image-Source: Movie: Oppenheimer (2023) by Christopher Nolan).

To understand why null exists at all, we must travel back to 1965, when the null reference was invented by Tony Hoare. In a renowned lecture in 2009, while accepting the Turing Award, Hoare famously referred to the creation of null references as his “billion-dollar mistake”:

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). 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

Null references are a common source of software errors, such as null pointer exceptions, which can cause system crashes and security issues. When Tony Hoare introduced null references, his motivation was practical: it seemed like a simple way to indicate that a reference didn’t have a valid value. However, this seemingly straightforward choice has had lasting consequences, resulting in decades of costly bug fixes and the need for complex security measures. Looking back, Hoare expressed regret for his decision, admitting that he couldn’t have foreseen the problems it would create. Today, his opinion on null references is clear:

Null references have been a historically bad idea.Tony Hoare

The luminaries opinion Link to heading

Who is Tony Hoare? Tony Hoare is a British computer scientist who gained widespread recognition for his contributions to the field. He is best known for creating the Quicksort algorithm, one of the most efficient sorting methods, and Hoare logic, which allows the correctness of algorithms to be proven. He also developed a process algebra that influenced programming languages like Ada, Occam, and Go. In 1980, Hoare received the Turing Award, often referred to as the “Nobel Prize of Computer Science”, for his work on the definition and design of programming languages.

Sir Charles Antony Richard Hoare

Sir Charles Antony Richard Hoare

Why do I mention all of this? Because when a luminary in computer science like Tony Hoare declares, that null references are a bad idea, it carries significant weight — particularly given that he himself was the one who introduced them. And he is not alone in his opinion. As Hoare noted in his Turing Award lecture, Edsger W. Dijkstra also regarded null references as a bad idea.

Edsger Wybe Dijkstra

Edsger Wybe Dijkstra

Dijkstra was another luminary in the field of computer science. He was a pioneer of structured programming and famous for the Dijkstra’s algorithm. In 1972, he received the Turing Award as well. Dijkstra used humor to illustrate the problem with null references in programming. In a metaphor, he described objects as bachelors and null as a person: “If you have a null reference, then every bachelor who you represent in your object structure will seem to be married polyandrously to the same person Null”.

If you search the internet, you’ll find many people who view the use of null references as bad coding practice. One of them is Yegor Bugayenko. In his video on null references, Yegor presents insightful, practical code examples that highlight the issues associated with working with null values, while also offering effective solutions. The following chapters summarize Yegor Bugayenko’s video and his perspectives on null references:

Nine reasons to avoid null references Link to heading

The following code snippet shows a method that fetches an employee object from a database:

public Employee getByName(String name){
    int id = database.find(name);
    if(id < 0){
        return null;
    }
    return new Employee(id);
}

If the database does not find the employee, the method returns null instead of returning an instance of Employee. This is terribly wrong! But why? Here are 9 reasons:

1. Because it may cause many nullpointer exceptions in the code.

For example:

getByName("Jeffrey").salary();

If there is no employee with the name “Jeffrey” in the database, then the above code throws a NullpointerException.

There is no such thing as:

null.salary();

2. Because it is technically difficult to trace the causes of nullpointer exceptions.

Most of the time, there is a lack of context in stack traces. Usually, the stack trace points to the line where the NullpointerException happened but not to the specific variable or expression that was null. Modern programs with complex and nested code structures, with delayed execution and with lambdas make it even harder to debug.

3. Because null references destroy our trust to the objects we are working with.

When we do not use null in our code, we can trust, that objects will be objects. We can trust that objects will look like objects and that objects will behave like objects. However, when we introduce null references into our code, then we cannot trust our objects anymore. They are no longer reliable or self-sufficient.

Let’s assume we have a method that claims to return an object. If this method unexpectedly returns something that isn’t an object, it disrupts our entire reasoning. We can no longer be certain about what is null and what is not null, leaving us in a state of uncertainty.

Then we start to suspect all objects that they are no objects anymore, even though they might actually still be. We suspect all the variables and everything that we are working with that it is not good enough. We cannot know whether we can “touch” the variables or not because they could be null and then the code breaks if we try to access them. In this way objects are not real objects anymore and they are not following the contract we are expecting.

4. Because you have to insert countless null checks in your code.

The previously mentioned trust issues bring another disadvantage: You always have to check if variables are null before you can do something with them. You have to add those null-checks everywhere to make sure that your program works reliable and avoids NullpointerExceptions. In this way, the code base grows and grows and the methods/classes become longer and longer. The code becomes cluttered, unclear and hard to understand/maintain. This is the opposite of the “Clean Code” approach!

5. Because null references violate the core idea of encapsulation.

When a method can return null instead of an actual object, the calling code must explicitly check for null to avoid exceptions. This shifts the responsibility of managing the internal state of software entities to the external code. For example:

Object obj = anotherObject.someMethod();

if (obj != null) {
    obj.doSomething();
} else {
    // Handle null case
}

Instead of encapsulating behavior, the caller has to manage an exceptional case (null) that ideally should have been handled inside the method someMethod(). In this way, null references violate encapsulation. Internal states are exposed and external code is forced to handle the consequences. This undermines the very essence of objects being self-contained and reliable entities. In the code example above, the object anotherObject is not self-contained. With proper encapsulation, objects manage their behavior internally and provide meaningful defaults or throw specific exceptions if something goes wrong. Returning null avoids this responsibility, leaving the caller to guess what went wrong or what to do next.

6. Because null references are completely against the object oriented paradigm.

Null references break encapsulation and the trust in objects. In OOP, objects should model Real-World-Concepts. Null however, does not represent anything meaningful in the real world. For instance, an object like Car represents a car, but null doesn’t represent “no car” in an intuitive way — it’s simply the absence of a reference.

In my opinion, polymorphism is the key principle of OOP (I explain why in this article). However null even contradicts polymorphism. Polymorphism lets objects define how they behave using interfaces and inheritance. Null references break this because null doesn’t have any behavior. Null can’t inherit from classes! Null can’t implement interfaces!

Furthermore, introducing null often leads to procedural-style code, with if-statements checking for null scattered throughout the codebase. This undermines the OO principle of objects interacting through well-defined methods, making the code less modular and harder to maintain.

7. Because null is an ancient concept that originated in low-level programming languages.

Null was originally introduced in languages that use pointers, like C for instance. However, when programming in Java, we don’t think about pointers or memory. Instead, we deal with objects and references. In “Object-Thinking”, we view objects as “living organisms” or “creatures.” The thinking is about objects, the messages you send to them, and the reactions they send back to us. But null doesn’t have any reaction since it is not an object!

8. Because a lot of computer viruses are designed to exploit null references.

Those viruses hope that some software forgets to check the null references and that they can reach that part in the program.

9. Because null spreads like a disease.

Null behaves like a disease in our applications. If we allow it to enter our software, it grows and eventually infects the entire code. For instance, if a method returns null, you have to insert null-checks for it. These checks spread as more methods interact with the null-returning method, leading to a domino effect. The need to protect against null infects every layer of the system. When one method allows null as a valid return value or parameter, others must follow suit.

Conclusion

What do these 9 reasons tell us? When it comes to null, there is no middle ground — it’s either black or white. Using null means the code is flawed and needs refactoring. Avoiding null results in clean, reliable code.

Two good alternatives to null references Link to heading

Now we know, that null references are really bad and that they have to be avoided at all cost. But how? What should we use instead of null references? Yegor presents two alternatives:

Alternative #1: Throw a Custom Exception instead of returning null: Link to heading

Given the above example, you could simply throw your own exception:

public Employee getByName(String name){
    int id = database.find(name);
    if(id < 0){
        throw new EmployeeNotFoundException(
            "I'm sorry, but the employee is not found"
        )
    }
    return new Employee(id);
}

Yegor’s suggestion of throwing an exception also reminds me on the principle “Crash Early!”, which was formulated in the book The Pragmatic Programmer (on page 120-121). When your code encounters something that should have been impossible, the program is no longer reliable. Anything it does after that point becomes questionable, so it’s better to stop it as soon as possible. A program that has crashed, typically causes less harm than one that continues to run in a broken state.

Alternative #2: Return a Null-Object instead of null: Link to heading

You could use the so-called “Null Object Design Pattern”. Given the above example, this solution could look like that:

public Employee getByName(String name){
    int id = database.find(name);
    if(id < 0){
        return new NoEmployee();
    }
    return new Employee(id);
}

In this case, the class NoEmployee could implement the same interface as Employee but in a different way than the Employee.

So how does the null object pattern work?

Components of Null Object Design Pattern. The Client depends on the DependencyBase. The Dependency and the NullDependency implement this interface in different ways. Source: geeksforgeeks.org

Components of Null Object Design Pattern. The Client depends on the DependencyBase. The Dependency and the NullDependency implement this interface in different ways. Source: geeksforgeeks.org

A code-example of this pattern in action (Source: Wikipedia):

public interface Animal {
    void makeSound();
}

public class Dog implements Animal {
    public void makeSound() {
        System.out.println("woof!");
    }
}

public class NullAnimal implements Animal {
    public void makeSound() {
        // do nothing
    }
}

If you are currently working with Animal objects but there is no animal available, you can use a NullAnimal instead of null. This represents the absence of an animal and simply does nothing (see the makeSound() method). The big advantage is that this approach does not trigger a NullPointerException in your code. Of course, you could also throw a custom exception in the makeSound() method of the NullAnimal:

public class NullAnimal implements Animal {
    public void makeSound() throws NullAnimalSoundException {
        throw new NullAnimalSoundException("A NullAnimal cannot make any sound!");
    }
}

In this situation you might ask yourself: “Doesn’t a null object just delay the failure to a later point in the code?”. And the answer is: Yes, definately! In this case we just delay the moment when the exception is thrown. But the advantage here is, that we as software engineers can “control” the process in this way. And we can throw meaningful exceptions on our own. Exceptions that contain better information on the failure scenario.

These two alternatives (throwing custom exceptions and using Null-Objects) are the best ways to deal with null references! In most situations probably Alternative#1 is the best solution: just throwing an exception immediately.

⚠️ Using Optionals is not an alternative ⚠️ Link to heading

In Yegor’s opinion, the use of java.util.Optional is not a solution at all. Optionals would deal with the problem scenario in the following way:

public Employee getByName(String name){
int id = database.find(name);
if(id < 0){
    return new Optional();
}
return new Optional(Employee(id));
}
Optional<Employee> opt = getByName("Jeffrey");
if(opt.has()){
    opt.get().salary();
}else{
    System.out.println("sorry");
}

An Optional is kind of a container including one or zero elements.

Using Optionals is not a good idea. Why? Because it is not a solution for null references — it is the same as null references. The optional container is not an object by itself. It is just a temporary memory structure which holds a real object. You still have to check if the optional contains an Employee or if it has no Employee. It is something that is presented as the solution of the null reference problem but actually it is not.

Excursus: Kotlin’s solution Link to heading

Kotlin has attempted to solve the problem by making variables either nullable or non-nullable. Types are non-nullable by default. If you want a variable to hold a null value, you must explicitly declare it as nullable using ?.

// Non-nullable variable
val nonNullable: String = "Hello, Kotlin"

// Nullable variable
val nullable: String? = null

The advantage: The question mark immediately shows which object can be trusted and which object cannot. If you try to call a method on a nullable object, the compiler warns you and advises you to insert a null-check.

Kotlin introduced safe calls ?. and the elvis operator ?: as well:

// if nullable evaluates to null, then .length is not executed and null returned instead.
val length: Int? = nullable?.length

// provides the default value 0 when nullable?.length evaluates to null.
val defaultLength: Int = nullable?.length ?: 0

Refutation of the counterarguments Link to heading

There might be some counterarguments against the usage of Alternative #1 and Alternative #2 in your mind. Maybe this chapter can still convince you that they are good solutions. 😉

Counterargument #1: Link to heading

Why throw a custom exception when we could just let the NullPointerException occur? In the end, the result is the same, right?

No! There is a big difference between a NullpointerException raised by the system or a EmployeeNotFoundException thrown manually by us developers. The NullpointerException is thrown by the JVM, which is outside of our developer’s scope, which means we can’t control it. But the EmployeeNotFoundException is thrown by our objects so we are in charge, we can control it, we encapsulate this behaviour. Our objects control it and not the Java Runtime Environment. Furthermore, the EmployeeNotFoundException can contain a custom error message written by the developer which is also important for understanding the whole situation.

However, it is important that the program flow should not be controlled via exceptions! This is a trap, that developers often fall for.

Counterargument #2 Link to heading

Null is fast and exceptions are slow (regarding code performance)!

The following two code snippets illustrate this argument. The argument is that the first code snippet is significantly less performant than the second one.

try{
    Employee jeff = getByName("Jeffrey");
    System.out.println(jeff.salary());
}catch(EmployeeNotFoundException ex){
     System.out.println("no jeffrey here");
}
Employee jeff = getByName("Jeffrey");
if(jeff == null){
    System.out.println("no jeffrey here");
}else{
    System.out.println(jeff.salary());
}

This is true! The second code snippet is way faster than the first one! Every exception thrown requires significant effort from the compiler and runtime. The runtime must collect information about the cause of the exception and where it was thrown, build the stack trace, prepare the necessary details, halt the program’s normal execution, and transition to the exceptional flow,…

However, the speed of an application is far less important in modern software than its maintainability. It is much more important to have software that is easy to maintain, understand, modify, adapt, and cleanly coded than to focus on its performance.

Using null is, in most cases, much faster than using exceptions. While null makes the computer’s job easier, it complicates the developer’s work. For companies, it is far more expensive to deal with an unmaintainable application than to invest in new, more efficient hardware resources. This is also exactly the thing that the whole Clean Architecture Book taught me. Even within the first 12 pages, Robert C. Martin emphasizes the importance of maintainability and provides diagrams illustrating how much money companies have lost due to unmaintainable software.

Counterargument #3 Link to heading

Some classes are not suitable for creating a null object. Consider the String class as an example. This is a final class which means it can’t be extended or reimplemented by somebody else. In this case, the null object pattern is not applicable. When you have a method that is supposed to return a String, you do not have the option to return a null class, such as a null string or something similar.

It is not possible to use null objects everywhere but if it is not possible, then make it possible! In this specific case you could wrap or decorate the String class. Introduce your own class Text for instance which internally uses a String. For the representation of null you could use your NoText class and for existing texts you could use the normal Text class. But never ever use null. There is always an alternative to that!

Conclusion Link to heading

I hope you enjoyed my article about null references. Whether you’re a fan or a critic of null, I’d like to leave you with one final thought: I believe it’s incredibly important to question common development practices that we usually take for granted. Some things, like the use of null, are often accepted by developers as a given. But just because something exists and is used by a lot of people doesn’t automatically mean that it’s a good thing. So next time you’re at the office, grab a coffee and chat with your fellow programmers — even about the overlooked aspects of programming. ☕😊

Reference Link to heading

Books Link to heading

  • Robert C. Martin: Clean Architecture: A Craftsman’s Guide to Software Structure and Design (Robert C. Martin Series), Publisher: Pearson, Year: 2017, ISBN: 0134494164

  • Andrew Hunt, David Thomas: The Pragmatic Programmer: From Journeyman to Master, Publisher: Addison-Wesley, Year: 2000, ISBN: 020161622X

Videos Link to heading

Blog Articles Link to heading

Websites Link to heading