Core Java

Why NULL is Bad?

A simple example of NULL usage in Java:
 
 
 
 
 
 
 
 
 
 

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

What is wrong with this method?

It may return NULL instead of an object — that’s what is wrong. NULL is a terrible practice in an object-oriented paradigm and should be avoided at all costs. There have been a number of opinions about this published already, including Null References, The Billion Dollar Mistake presentation by Tony Hoare and the entire Object Thinking book by David West.

Here, I’ll try to summarize all the arguments and show examples of how NULL usage can be avoided and replaced with proper object-oriented constructs.

Basically, there are two possible alternatives to NULL.

The first one is Null Object design pattern (the best way is to make it a constant):

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

The second possible alternative is to fail fast by throwing an Exception when you can’t return an object:

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

Now, let’s see the arguments against NULL.

Besides Tony Hoare’s presentation and David West’s book mentioned above, I read these publications before writing this post: Clean Code by Robert Martin, Code Complete by Steve McConnell, Say “No” to “Null” by John Sonmez, Is returning null bad design? discussion at StackOverflow.

Ad-hoc Error Handling

Every time you get an object as an input you must check whether it is NULL or a valid object reference. If you forget to check, a NullPointerException (NPE) may break execution in runtime. Thus, your logic becomes polluted with multiple checks and if/then/else forks:

// this is a terrible design, don't reuse
Employee employee = dept.getByName("Jeffrey");
if (employee == null) {
  System.out.println("can't find an employee");
  System.exit(-1);
} else {
  employee.transferTo(dept2);
}

This is how exceptional situations are supposed to be handled in C and other imperative procedural languages. OOP introduced exception handling primarily to get rid of these ad-hoc error handling blocks. In OOP, we let exceptions bubble up until they reach an application-wide error handler and our code becomes much cleaner and shorter:

dept.getByName("Jeffrey").transferTo(dept2);

Consider NULL references an inheritance of procedural programming, and use 1) Null Objects or 2) Exceptions instead.

Ambiguous Semantic

In order to explicitly convey its meaning, the function getByName() has to be named getByNameOrNullIfNotFound(). The same should happen with every function that returns an object or NULL. Otherwise, ambiguity is inevitable for a code reader. Thus, to keep semantic unambiguous, you should give longer names to functions.

To get rid of this ambiguity, always return a real object, a null object or throw an exception.

Some may argue that we sometimes have to return NULL, for the sake of performance. For example, method get() of interface Map in Java returns NULL when there is no such item in the map:

Employee employee = employees.get("Jeffrey");
if (employee == null) {
  throw new EmployeeNotFoundException();
}
return employee;

This code searches the map only once due to the usage of NULL in Map. If we would refactor Map so that its method get() will throw an exception if nothing is found, our code will look like this:

if (!employees.containsKey("Jeffrey")) { // first search
  throw new EmployeeNotFoundException();
}
return employees.get("Jeffrey"); // second search

Obviously, this is method is twice as slow as the first one. What to do?

The Map interface (no offense to its authors) has a design flaw. Its method get() should have been returning an Iterator so that our code would look like:

Iterator found = Map.search("Jeffrey");
if (!found.hasNext()) {
  throw new EmployeeNotFoundException();
}
return found.next();

BTW, that is exactly how C++ STL map::find() method is designed.

Computer Thinking vs. Object Thinking

Statement if (employee == null) is understood by someone who knows that an object in Java is a pointer to a data structure and that NULL is a pointer to nothing (0x00000000, in Intel x86 processors).

However, if you start thinking as an object, this statement makes much less sense. This is how our code looks from an object point of view:

- Hello, is it a software department?
- Yes.
- Let me talk to your employee "Jeffrey" please.
- Hold the line please...
- Hello.
- Are you NULL?

The last question in this conversation sounds weird, doesn’t it?

Instead, if they hang up the phone after our request to speak to Jeffrey, that causes a problem for us (Exception). At that point, we try to call again or inform our supervisor that we can’t reach Jeffrey and complete a bigger transaction.

Alternatively, they may let us speak to another person, who is not Jeffrey, but who can help with most of our questions or refuse to help if we need something “Jeffrey specific” (Null Object).

Slow Failing

Instead of failing fast, the code above attempts to die slowly, killing others on its way. Instead of letting everyone know that something went wrong and that an exception handling should start immediately, it is hiding this failure from its client.

This argument is close to the “ad-hoc error handling” discussed above.

It is a good practice to make your code as fragile as possible, letting it break when necessary.

Make your methods extremely demanding as to the data they manipulate. Let them complain by throwing exceptions, if the provided data provided is not sufficient or simply doesn’t fit with the main usage scenario of the method.

Otherwise, return a Null Object, that exposes some common behavior and throws exceptions on all other calls:

public Employee getByName(String name) {
  int id = database.find(name);
  Employee employee;
  if (id == 0) {
    employee = new Employee() {
      @Override
      public String name() {
        return "anonymous";
      }
      @Override
      public void transferTo(Department dept) {
        throw new AnonymousEmployeeException(
          "I can't be transferred, I'm anonymous"
        );
      }
    };
  } else {
    employee = Employee(id);
  }
  return employee;
}

Mutable and Incomplete Objects

In general, it is highly recommended to design objects with immutability in mind. This means that an object gets all necessary knowledge during its instantiating and never changes its state during the entire lifecycle.

Very often, NULL values are used in lazy loading, to make objects incomplete and mutable. For example:

public class Department {
  private Employee found = null;
  public synchronized Employee manager() {
    if (this.found == null) {
      this.found = new Employee("Jeffrey");
    }
    return this.found;
  }
}

This technology, although widely used, is an anti-pattern in OOP. Mostly because it makes an object responsible for performance problems of the computational platform, which is something an Employee object should not be aware of.

Instead of managing a state and exposing its business-relevant behavior, an object has to take care of the caching of its own results — this is what lazy loading is about.

Caching is not something an employee does in the office, does he?

The solution? Don’t use lazy loading in such a primitive way, as in the example above. Instead, move this caching problem to another layer of your application.

For example, in Java, you can use aspect-oriented programming aspects. For example, jcabi-aspects has @Cacheable annotation that caches the value returned by a method:

import com.jcabi.aspects.Cacheable;
public class Department {
  @Cacheable(forever = true)
  public Employee manager() {
    return new Employee("Jacky Brown");
  }
}

I hope this analysis was convincing enough that you will stop NULL-ing your code!

Related Posts

You may also find these posts interesting:

Reference: Why NULL is Bad? from our JCG partner Yegor Bugayenko at the About Programming blog.

Yegor Bugayenko

Yegor Bugayenko is an Oracle certified Java architect, CEO of Zerocracy, author of Elegant Objects book series about object-oriented programing, lead architect and founder of Cactoos, Takes, Rultor and Jcabi, and a big fan of test automation.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

9 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Natha
Natha
9 years ago

Java 8 : java.util.Optional + functionnal programming is a great solution for the “possibly null” object.

Yannick Majoros
Yannick Majoros
9 years ago
Reply to  Natha

Right, you summed up in 3 words what I tried to explain in 20 lines ;-)

Natha
Natha
9 years ago

I love K.I.S.S. :)

Roni
Roni
9 years ago

returning exception instead of null might have a huge performance implications. beside that, I’m not sure how wrapping each function call with try catch simplify the code.

Yegor Bugayenko
9 years ago
Reply to  Roni

Yes, totally agree. It is almost exactly the same as returning an Iterable. You can also check whether it has anything or it is empty.

Yegor Bugayenko
9 years ago
Reply to  Roni

Why do you need to wrap every function call with try/catch? Let your exceptions bubble up to the highest level. Exception means something is broken: you’re trying to get a value and there is nothing available for you. It is an exception and the entire application should crash (“fail fast”, remember?).

Yannick Majoros
Yannick Majoros
9 years ago

This article at least makes this point: the subject needs understanding and clarification. Null has its problems. Null objects make them worse. You should check for null when you know it’s expectable. Having a null object that throws exceptions for every method will make an application crash the same way as with nulls and NPEs. No problem solved with that. The simplest solution is to just to return null when appropriate, knowing its limitations. Null has the semantics you give. No more, no less. If getByName() doesn’t always return a result, it’s probably appropriate to return null to express that.… Read more »

Hans Jørgen Pedersen
Hans Jørgen Pedersen
9 years ago

Nice article, including the last comment from Yannick Majoros. I want to add my voice to this: I am not a java coder; I am an SQL nerd, used to dealing with legacy SQL code written by someone who was a java coder! You beat around the bush in one particular aspect here: “NULL” in a data layer is NOT “nothing” (or, I quote: “(0x00000000, in Intel x86 processors).”). So of course, if you “point to nothing” things get messy! “NULL” in a data layer has the interpretation “unknown”. A crucial part of the SQL language is understanding that the… Read more »

Yannick Majoros
Yannick Majoros
9 years ago

Hi Hans, Good comment, you made your point concerning SQL and the fact that it can mean “unknown” rather than “absent”. In java, the specification doesn’t say whether null means “unknown”, “absent” or anything else. In fact, it can mean anything. That’s dependent on your domain. The API writer needs to specify whether null’s are allowed, and what it means if they are. My point was that it’s valid, and most of the time easier, to allow null in simple, well-understood situations. I’m quite against “null objects” (actual instances of classes, but without any real properties). What about their relations,… Read more »

Back to top button