Why I never null-check parameters

Writing code to make sure that input parameters are not null does not make Java code “safer”, exactly the opposite, it makes code less readable and less safe.

Code with null-checks is less readable

It would be difficult to argue that null-checks are attractive. Most of the time it’s just boilerplate code which contributes nothing to the “logic” of the method. It is a purely technical construct. Like this one:

@Override
public void getLock(String name, Handler<AsyncResult<Lock>> handler) {
    Objects.requireNonNull(name, "name");   
    Objects.requireNonNull(handler, "handler");   
    getLockWithTimeout(name, DEFAULT_LOCK_TIMEOUT, handler);
}

This method from the otherwise very cool vert.x project has 3 lines of code, 2 of which is checking for null, so it makes this method somewhat harder to read. These sorts of checks are even more damaging when they do actually contribute to the method’s logic:

@Override
public Object getValue(final ELContext context, Object base, Object property) {
   BeanManagerImpl beanManager = getManager(context);
   if (property != null) {
      String propertyString = property.toString();
      ElLogger.LOG.propertyLookup(propertyString);
      Namespace namespace = null;
      if (base == null) {
         ... 
      }
...

This snippet from the popular Weld Project shows how null checks can increase the cyclomatic complexity of code. In this case nulls are actually expected and act as flag arguments.

The reason null-checks exist

The simple reason to check for nulls is the fear of the NullPointerException, ingrained in every Java developer. There is a famous quote from Sir Thomas Hoare, who “invented” the null reference saying:

I call it my billion-dollar mistake.

Sir Tony Hoare

Referring to the many crashes, errors, endless hours of debugging and other resources lost or spent because of null.

Checking for null parameters however is not the correct reaction to this admitted shortcoming of the type-system, because it ignores that there are actually two distinct categories of null-related issues.

The first category is the simple programming error. Errors and bugs can always happen of course, for example that some parameter somewhere becomes null, even if the code requires a non-null reference. Java has already a mechanism to catch these types of errors by throwing a NullPointerException. Yes, in these cases this is not a bad thing. The bug needs to be visible to be easily corrected.

The second category is the problematic one. It is when code deals with null values, often assigning meaning to the null value, like the Weld code with the conditions above. In these cases there will be no exceptions generated, there will only be some different behavior. It wouldn’t be visible if some of the parameters were null by accident, rather, there would be perhaps some business-level issue. Instead of a clear exception to follow, this would probably require debugging or a more in-depth analysis.

The cost of null-checks

Beyond the obvious cost that the additional boiler-plate code introduces, the much greater cost of null-checks is that it legalizes nulls. It makes it OK to pass and receive nulls, thereby increasing the code surface where nulls can cause issues.

It can also hide these issues caused, by creating code that can continue to work with a null value in some form, implicitly altering the logic of the code.

Removing parameter null-checks

In a perfect world it should not be possible to pass or return null values, at least not in public methods. This is one area where Kotlin, Haskell and others are clearly better designed than Java. We can however pretend that nulls don’t exist. The question is: is that practical?

In the case of programming errors, it is very easy to find out that nulls were passed on. If there are no null-checks and no alternative paths for execution, it will sooner or later lead to a NullPointerException. This will be clearly visible, therefore easy to find and correct.

So the above code:

@Override
public void getLock(String name, Handler<AsyncResult<Lock>> handler) {
    Objects.requireNonNull(name, "name");
    Objects.requireNonNull(handler, "handler");
    getLockWithTimeout(name, DEFAULT_LOCK_TIMEOUT, handler);
}

Should simply be:

@Override
public void getLock(String name, Handler<AsyncResult<Lock>> handler) {
   getLockWithTimeout(name, DEFAULT_LOCK_TIMEOUT, handler);
}

This strategy however does not work if null is a legal value of some parameter or parameters. The simplest solution for these cases is to split the method into multiple ones with each requiring all its parameters. This would transform the Weld code above where the base parameter is not required:

public Object getValue(final ELContext context, Object base, Object property) // base not required

To:

public Object getObjectValue(final ELContext context, Object base, Object property) // base required ... 
public Object getRootValue(final ELContext context, Object property) // base omitted

An alternative solution is to allow the “root” as base parameter, in which case the method does not need to be split, and the base can be a required parameter.

Sometimes there can be more than 1 or 2 parameters in a signature that are not required. This is often seen in constructors. In these cases, the Builder Pattern might help.

Getting rid of null as return value

Removing these checks is only practical if the caller doesn’t have to check either. This is only possible if the caller can freely pass in results from other method calls. Therefore methods shouldn’t return null. Every method that returns null legally needs to be changed depending on what meaning this null value carries. Most of the time it means that a certain object could not be found and the caller should react according to its own logic. In these cases the Optional class might help:

@Override
public Optional<Object> getValue(final ELContext context, Object base, Object property) {
    ...
    return Optional.empty();
 }

This way the possibility of a missing value is explicit and the caller is free to react however it pleases. Note on Optional: don’t use the get() method, use map(), orElse(), orElseGet(), ifPresent(), etc.

Sometimes returning null means that the caller should execute some default logic defined by the callee. Instead of pushing this responsibility to the caller, the method should return the default logic itself. Let’s take a look at an example from Weld again:

protected DisposalMethod<...> resolveDisposalMethod(...) {
    Set<DisposalMethod<...>> disposalBeans = ...;
    if (disposalBeans.size() == 1) {
       return disposalBeans.iterator().next();
    } else if (disposalBeans.size() > 1) {
       throw ...;
    }
    return null;
}

One could think of this method returning null to indicate that there is no disposal method defined for a bean instantiated by Weld (a dependency injection framework). But actually what this method wants to say is, that there is nothing to do for disposal. Instead of legalizing null for a lot of intermediary objects,  just so that some object can eventually check whether the returned DisposalMethod is null to do nothing, the method could just return a DisposalMethod that does nothing. Just have another implementation of it, like:

public final class NoDisposalMethod
       implements DisposalMethod {
    ...
    @Override
    public void invoke(...) {
       // Do nothing
    }
}

And then do:

protected DisposalMethod<...> resolveDisposalMethod(...) {
    ...
    return new NoDisposalMethod();
 }

This way, the method does not return null and nobody has to check for null either.

Leftovers

The unfortunate reality is that we can not ignore nulls everywhere. There are a lot of classes, objects, frameworks over which we have no influence. We have to deal with these. Some of these are in the JDK itself.

For these cases and for these cases only, null-checks are allowed, if only just to package them with Optional.ofNullable(), or check with Objects.requireNonNull().

Summary

There is a somewhat prevalent notion, that null-checks make Java code safer, but actually it makes code less safe and less readable.

We should instead pretend that nulls don’t exist and treat any occurrence of null as a programming error, at least in public method parameters or return values. Specifically:

  • Methods and constructors should not check for nulls
  • Methods should not return nulls

Instead:

  • Methods and constructors should assume that all parameters are non-null
  • Methods that allowed null should be redesigned or split into multiple methods
  • Constructors with multiple parameters that aren’t required should consider using the Builder Pattern.
  • Methods that returned null should return Optional, or a special implementation of whatever logic was expected on null previously.

6 thoughts on “Why I never null-check parameters

  1. It’s great to see you writing again. This blog has some of the best articles on programming I have ever read.

    Another source of nulls is data classes deserialized from JSONs, say, with jackson-databind. If JSON has some fields missing, the corresponding fields in the data classes will be null. There might even be some business rules about what needs to be done if certain fields are missing – then null checks seem to be inevitable. One way to fix this (and several other issues as well) is not to deserialize JSONs into data classes, but use some Java JSON object instead, say Jackson’s JsonNode. Then instead of if (response.getFoo().getBar() == null) doSomething(); we could do if (response.at("/foo/bar").isMissingNode()) doSomething();

    Or maybe there is a better way to eliminate nulls in this situation?

    Like

    1. Thanks for the kind words!

      You are absolutely right, that null-checks are almost inevitable when interfacing with libraries or frameworks that “misuse” null to actually carry some implicit meaning. To be fair, it is also a historical thing, there was no universal alternative to represent “nothing” before Optional came along in Java 8.

      I agree with you also on not using “data” oriented frameworks if at all possible, but to use some generic representation of Json as you said, and work with that. In general I would avoid any library that uses reflection (like ORMs) in the first place, since reflection itself is data oriented.

      Ideally there should be some Json library that uses Optional for returning potentially missing return values. That would be as easy to use as:

      response.at(“/foo/bar”).ifPresent(this::processFooBar); // Or similar

      There are also stream-based (sax-like) Json processors, that won’t build the whole Json model in memory (to avoid excessive memory usage or to be non-blocking), like that of vert.x. For these this issue doesn’t really exist, because you will get callbacks for each object parsed.

      But if none of these solutions is available, there’s no way around a null-check. For code that you don’t control (and can’t avoid) there’s nothing else to do.

      Like

  2. I absolutely agree. What you describe is a choice between “Fail safe” and “Fail fast”. Fail fast (no null checks) helps to discover bugs as soon as possible, instead of hiding them and trying to recover somehow – only the client should know how and if to recover from an exceptional situation.

    Like

    1. A null check on entering a method or constructor is per design a ‘fail fast’ approach! That is the reason they exist. You can rely on implicit ones that occur later when using the parameter, but the check at the beginning avoids to perform actions that will be wasted anyways.

      Like

  3. You write “it will sooner or later lead to a NullPointerException”. Later is extremely bad. If a method for example stores the data that is passed in in a database or file, which is read a month later, I will have no idea how and where that null came from. Fail fast!

    Like

    1. My point is, that if you pass nulls to some method you’ve already done something bad. You’re not fixing the problem where it occurs, just where it becomes visible. Fail faster!

      You don’t need null-checks, because you shouldn’t make nulls *legal*. You have to look at the whole context of the application instead of just individual methods.

      Check the nulls at the gate, what I called “leftovers” above, and then forget about them! It’ll get rid of most of the null-checks.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s