Avoid Check for Null Statement in Java

[[java null]]
=== 1. Overview

Generally, null variables, references, and collections are tricky to
handle in Java code. Not only are they hard to identify, but they’re
also complex to deal with.

As a matter of fact, any miss in dealing with null cannot be
identified at compile time and results in a NullPointerException at
runtime.

In this tutorial, we’ll take a look at the need to check for null in
Java and various alternatives that help us to avoid null checks in our
code.

[[NullPointer exception]]
=== 2. What Is NullPointerException?

According to the
Javadoc
for NullPointerException
, it’s thrown when an application attempts to
use null in a case where an object is required, such as:

  • Calling an instance method of a null object

  • Accessing or modifying a field of a null object

  • Taking the length of null as if it were an array

  • Accessing or modifying the slots of null as if it were an array

  • Throwing null as if it were a Throwable value

Let’s quickly see a few examples of the Java code that cause this
exception:

public void doSomething() {
    String result = doSomethingElse();
    if (result.equalsIgnoreCase("Success"))
        // success
    }
}

private String doSomethingElse() {
    return null;
}

Here, we tried to invoke a method call for a null reference. This
would result in a NullPointerException.

Another common example is if we try to access a null array:

public static void main(String[] args) {
    findMax(null);
}

private static void findMax(int[] arr) {
    int max = arr[0];
    //check other elements in loop
}

This causes a NullPointerException at line 6.

Thus, accessing any field, method, or index of a null object causes
NullPointerException, as can be seen from the examples above.

A common way of avoiding the NullPointerException is to check for
null:

public void doSomething() {
    String result = doSomethingElse();
    if (result != null && result.equalsIgnoreCase("Success")) {
        // success
    }
    else
        // failure
}

private String doSomethingElse() {
    return null;
}

In the real world, programmers find it hard to identify which objects
can be null. An aggressively safe strategy could be to
check null for every object. This, however, causes a lot of redundant
null checks and makes our code less readable.

In the next few sections, we’ll go through some of the alternatives in
Java that avoid such redundancy.

[[API contract]]
=== 3. Handling null Through the API Contract

As discussed in the last section, accessing methods or variables of
null objects causes a NullPointerException. We also discussed
that putting a null check on an object before accessing it
eliminates the possibility of NullPointerException.

However, often there are APIs that can handle null values. For
example:

public void print(Object param) {
    System.out.println("Printing " + param);
}

public Object process() throws Exception {
    Object result = doSomething();
    if (result == null) {
        throw new Exception("Processing fail. Got a null response");
    } else {
        return result;
    }
}

The print() method call would just print “null” but won’t throw an
exception. Similarly, process() would never return null in its
response. It rather throws an Exception.

So for a client code accessing the above APIs, there is no need for
null check.

However, such APIs must make it explicit in their contract. A common
place for APIs to publish such a contract is the JavaDoc
.

This, however, gives no clear indication of the API contract and thus
relies on the client code developers to ensure its compliance.

In the next section, we’ll see how a few IDEs and other development
tools help developers with this.

[[API contract]]
=== 4. Automating API Contracts

[[code analysis]]
==== 4.1. Using Static Code Analysis

Static code
analysis
tools help improve the code quality to a great deal. And a few
such tools also allow the developers to maintain the null contract.
One example is FindBugs.

FindBugs helps manage the null contract through
the @Nullable and @NonNull annotations.
We can use these
annotations over any method, field, local variable, or parameter. This
makes it explicit to the client code whether the annotated type can be
null or not. Let’s see an example:

public void accept(@Nonnull Object param) {
    System.out.println(param.toString());
}

Here, @NonNull makes it clear that the argument cannot
be null. If the client code calls this method without checking the
argument for null, FindBugs would generate a warning at compile
time.
 

[[IDE contracts]]
==== 4.2. Using IDE Support

Developers generally rely on IDEs for writing Java code. And features
such as smart code completion and useful warnings, like when a variable
may not have been assigned, certainly help to a great extent.

Some IDEs also allow developers to manage API contracts and thereby
eliminate the need for a static code analysis tool. IntelliJ IDEA
provides the @NonNull and @Nullable annotations.
To add the
support for these annotations in IntelliJ, we must add the following
Maven dependency:

<dependency>
    <groupId>org.jetbrains</groupId>
    <artifactId>annotations</artifactId>
    <version>16.0.2</version>
</dependency>

Now, IntelliJ will generate a warning if the null check is missing,
like in our last example.

IntelliJ also provides
Contract
annotation for handling complex API contracts.

5. Assertions

Until now, we’ve only talked about removing the need for null checks
from the client code. But, that is rarely applicable in real-world
applications.

Now, let’s suppose that we’re working with an API that cannot accept
null parameters or can return a null response that must be
handled by the client
. This presents the need for us to check the
parameters or the response for a null value.

Here, we can use Java Assertions
instead of the traditional null check conditional statement:

public void accept(Object param){
    assert param != null;
    doSomething(param);
}

In line 2, we check for a null parameter. If the assertions are
enabled, this would result in an 
*AssertionError.*

Although it is a good way of asserting pre-conditions like non-null
parameters, this approach has two major problems:

  1. Assertions are usually disabled in a JVM

  2. false assertion results in an unchecked error that is
    irrecoverable

Hence, it is not recommended for programmers to use Assertions for
checking conditions.
In the following sections, we’ll discuss other
ways of handling null validations.

[[coding practice]]
=== 6. Avoiding Null Checks Through Coding Practices

6.1. Preconditions

It’s usually a good practice to write code that fails early. Therefore,
if an API accepts multiple parameters that aren’t allowed to be null,
it’s better to check for every non-null parameter as a precondition
of the API.

For example, let’s look at two methods – one that fails early, and one
that doesn’t:

public void goodAccept(String one, String two, String three) {
    if (one == null || two == null || three == null) {
        throw new IllegalArgumentException();
    }

    process(one);
    process(two);
    process(three);
}

public void badAccept(String one, String two, String three) {
    if (one == null) {
        throw new IllegalArgumentException();
    } else {
        process(one);
    }

    if (two == null) {
        throw new IllegalArgumentException();
    } else {
        process(two);
    }

    if (three == null) {
        throw new IllegalArgumentException();
    } else {
        process(three);
    }
}

Clearly, we should prefer goodAccept() over badAccept().

As an alternative, we can also use
Guava’s Preconditions for
validating API parameters.

[[primitives wrappers]]
==== 6.2. Using Primitives Instead of Wrapper Classes

Since null is not an acceptable value for primitives
like int, we should prefer them over their wrapper counterparts like
Integer wherever possible.

Consider two implementations of a method that sums two integers:

public static int primitiveSum(int a, int b) {
    return a + b;
}

public static Integer wrapperSum(Integer a, Integer b) {
    return a + b;
}

Now, let’s call these APIs in our client code:

int sum = primitiveSum(null, 2);

This would result in a compile-time error since null is not a valid
value for an int.

And when using the API with wrapper classes, we get a
NullPointerException:

assertThrows(NullPointerException.class, () -> wrapperSum(null, 2));

There are also other factors for using primitives over wrappers, as we
covered in another tutorial,
Java Primitives
versus Objects
.

[[empty collections]]
==== 6.3. Empty Collections

Occasionally, we need to return a collection as a response from a
method. For such methods, we should always try to return an empty
collection instead of null:

public List<String> names() {
    if (userExists()) {
        return Stream.of(readName()).collect(Collectors.toList());
    } else {
        return Collections.emptyList();
    }
}

Hence, we’ve avoided the need for our client to perform a null check
when calling this method.

7. Using Objects 

Java 7 introduced the new Objects API. This API has several
static utility methods that take away a lot of redundant code. Let’s
look at one such method,
requireNonNull():

public void accept(Object param) {
    Objects.requireNonNull(param);
    // doSomething()
}

Now, let’s test the accept() method:

assertThrows(NullPointerException.class, () -> accept(null));

So, if null is passed as an argument, accept() throws a
NullPointerException.

This class also has isNull() and nonNull() methods that can be
used as predicates to check an object for null.

[[java optional]]
=== 8. Using Optional

Java 8 introduced a new
Optional API in the language.
This offers a better contract for handling optional values as compared
to null. Let’s see how Optional takes away the need
for null checks:

public Optional<Object> process(boolean processed) {
    String response = doSomething(processed);

    if (response == null) {
        return Optional.empty();
    }

    return Optional.of(response);
}

private String doSomething(boolean processed) {
    if (processed) {
        return "passed";
    } else {
        return null;
    }
}

By returning an Optional, as shown above, the process() method
makes it clear to the caller that the response can be empty and must be
handled at compile time.

This notably takes away the need for any null checks in the client
code. An empty response can be handled differently using the declarative
style of the Optional API:

assertThrows(Exception.class, () -> process(false).orElseThrow(() -> new Exception()));

Furthermore, it also provides a better contract to API developers to
signify to the clients that an API can return an empty response.

Although we eliminated the need for a null check on the caller of
this API, we used it to return an empty response. To avoid
this, Optional provides an ofNullable method that returns
an Optional with the specified value, or empty, if the value
is null
:

public Optional<Object> process(boolean processed) {
    String response = doSomething(processed);
    return Optional.ofNullable(response);
}

[[java library]]
=== 9. Libraries

9.1. Using Lombok

Lombok is a great
library that reduces the amount of boilerplate code in our projects. It
comes with a set of annotations that take the place of common parts of
code we often write ourselves in Java applications, such as getters,
setters, and toString(), to name a few.

Another of its annotations is @NonNull. So, if a project already
uses Lombok to eliminate boilerplate code, @NonNull can replace the
need for null checks
.

Before we move on to see some examples, let’s add a
Maven
dependency for Lombok:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.6</version>
</dependency>

Now, we can use @NonNull wherever a null check is needed:

public void accept(@NonNull Object param){
    System.out.println(param);
}

So, we simply annotated the object for which the null check would’ve
been required, and Lombok generates the compiled class:

public void accept(@NonNull Object param) {
    if (param == null) {
        throw new NullPointerException("param");
    } else {
        System.out.println(param);
    }
}

If param is null, this method throws a
NullPointerException. The method must make this explicit in its
contract, and the client code must handle the exception.

[[string utils]]
==== 9.2. Using StringUtils

Generally, String validation includes a check for an empty value in
addition to null value. Therefore, a common validation statement would
be:

public void accept(String param){
    if (null != param && !param.isEmpty())
        System.out.println(param);
}

This quickly becomes redundant if we have to deal with a lot of
String types.
This is where StringUtils comes handy. Before we
see this in action, let’s add a Maven dependency for
commons-lang3:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.8.1</version>
</dependency>

Let’s now refactor the above code with StringUtils:

public void accept(String param) {
    if (StringUtils.isNotEmpty(param))
        System.out.println(param);
}

So, we replaced our null or empty check with a static utility
method isNotEmpty(). This API offers other
powerful utility
methods
for handling common String functions.

[[null handling]]
=== 10. Conclusion

In this article, we looked at the various reasons
for NullPointerException and why it is hard to identify. Then, we
saw various ways to avoid the redundancy in code around checking for
null with parameters, return types, and other variables.

All the examples are available on
GitHub.

Leave a Reply

Your email address will not be published.