Mind the context of the null pointers

Alonso Del Arte
4 min readSep 13, 2019

--

Photo by Raquel Pedrotti on Unsplash

A lot of Java developers seem to have an almost superstitious attitude towards the NullPointerException. Some of them seem to wish that the Java programming language forbade null pointers altogether.

Eliminating or at least reducing the incidence of null pointers was a major concern in the design of both Scala and Kotlin (two programming languages for the Java Virtual Machine).

More important than the occurrence of a NullPointerException is when and where it occurs. In development, it can point up a silly and easily fixed mistake. In production, it might be cause to fire someone.

Of course it’s scary to think that a NullPointerException could get you fired. But with test-driven development and robust quality assurance, you can be confident that the dreaded NullPointerException will only occur in development, not production.

Here’s a toy example to illustrate: bank accounts. Consider the following abstract class:

public abstract class BankAccount {    public BankAccount(Entity primary, Entity secondary,
String label, Deposit initialDeposit) {
this.accountBalance = INITIALIZATION_ACCOUNT_BALANCE;
this.processDeposit(initialDeposit);

this.accountNumber = getNewAccountNumber();
this.primaryAccountHolder = primary;
this.noSecondaryAccountHolderFlag = (secondary == null);
this.secondaryAccountHolder = secondary;
this.accountLabel = label;
this.accountHistory = new ArrayList<>();
this.accountBeneficiary = null;
}
}

I was going to use this to illustrate how to use abstract classes to avoid unnecessary duplication of common functionality among similar classes. But since that’s not relevant for this example, I’ve left out the duplication.

Here’s the concrete class CheckingAccount:

public class CheckingAccount extends BankAccount {    private ArrayList<Check> checksList;    private SavingsAccount assocSav;    public CheckingAccount(Entity primary, Deposit initialDeposit) {
this(primary, null, "Primary Checking", initialDeposit);
}
public CheckingAccount(Entity primary, Entity secondary,
String label, Deposit initialDeposit) {
super(primary, secondary, label, initialDeposit);
this.checksList = new ArrayList<>();
this.assocSav = null;
}
}

As you can already see, there are three fields that can be null for a checking account: the field for the secondary account holder, the field for the beneficiary, and the field for the associated savings account.

But that’s actually not what’s going to get us into trouble when we run the tests for CheckingAccount.

Testsuite: bankaccounts.CheckingAccountTest
Tests run: 0, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.775 sec
Testcase: bankaccounts.CheckingAccountTest: Caused an ERROR
null
java.lang.NullPointerException
at bankaccounts.BankAccount.processDeposit(BankAccount.java:53)
at bankaccounts.BankAccount.<init>(BankAccount.java:84)
at bankaccounts.SavingsAccount.<init>(SavingsAccount.java:22)
at bankaccounts.SavingsAccount.<init>(SavingsAccount.java:18)
at bankaccounts.CheckingAccountTest.setUpClass(CheckingAccountTest.java:41)
Test bankaccounts.CheckingAccountTest FAILED
test:
Deleting: C:\Users\AL\AppData\Local\Temp\TEST-bankaccounts.CheckingAccountTest.xml
BUILD SUCCESSFUL (total time: 3 seconds)

The exception message “null” is useless. The stack trace, on the other hand, very helpfully points us to the problem in BankAccount.processDeposit():

    public final void processDeposit(Deposit deposit) {
this.accountBalance =
this.accountBalance.plus(deposit.getTransactionAmount());
this.accountHistory.add(deposit);
}

What happened here was that this procedure attempted to add a Deposit object to accountHistory before accountHistory was even initialized. So the actual problem is in the constructor. The fix is easy:

    public BankAccount(Entity primary, Entity secondary,
String label, Deposit initialDeposit) {
this.accountBalance = INITIALIZATION_ACCOUNT_BALANCE;
this.accountNumber = getNewAccountNumber();
this.primaryAccountHolder = primary;
this.noSecondaryAccountHolderFlag = (secondary == null);
this.secondaryAccountHolder = secondary;
this.accountLabel = label;
this.accountHistory = new ArrayList<>();
this.processDeposit(initialDeposit);
this.accountBeneficiary = null;
}

We just have the constructor initialize accountHistory and then process the initial deposit. Now we can run the tests and get back to work on the actual logic of the program.

Testsuite: bankaccounts.CheckingAccountTest
Savings account balance: $10000.00 prior to test
Checking account balance: $1500.00 prior to test
Verifying the same deposit can't be made twice...
Savings account balance: $10000.00 after test
Checking account balance: $2810.72 after test
Testcase: testDoubleDeposit(bankaccounts.CheckingAccountTest): FAILED
expected:<$2155.36> but was:<$2810.72>
junit.framework.AssertionFailedError: expected:<$2155.36> but was:<$2810.72>
at bankaccounts.CheckingAccountTest.testDoubleDeposit(CheckingAccountTest.java:79)

And so on and so forth. I have a GitHub repository for the source and tests of this toy example.

I emphasize that this is a toy example. You could make this sort of mistake, but if it happens this early in the process, the problem would surely be detected long before going to production, even without automated testing.

It’s more worrisome to make this sort of silly mistake in a more evolved project. What if the problem lurks in an obscure corner of the system?

This is part of the reason why good test coverage is important. If every line in which a NullPointerException could theoretically arise is covered by a test, and all the tests are running and passing, then you don’t have to worry that an unexpected NullPointerException could occur in production.

In general this applies to all exceptions. It’s just that NullPointerException is the exception that seems to occur the most often, and for the widest variety of error patterns, according to our own Andras Horvath.

I hope this helps you get a perspective on the NullPointerException as a useful debugging tool rather than something to dread.

--

--

Alonso Del Arte
Alonso Del Arte

Written by Alonso Del Arte

is a Java and Scala developer from Detroit, Michigan. AWS Cloud Practitioner Foundational certified

No responses yet