How to Solve a NullPointerException Correctly

In Java, one of the first exceptions everyone sees is the NullPointerException.

Unfortunately, it took me some time to understand how to solve it correctly.

The worst of the NullPointerException is that it can happen everywhere. And putting a big try/catch to catch all the NullPointerException will be worse.

The Problem

Let’s see a simple example.

public BigDecimal computeTotalPrice(List<Product> products) {
  
  BigDecimal totalPrice = BigDecimal.ZERO;

  for (Product product : products) {

    BigDecimal unitPrice = product.getUnitPrice();

    totalPrice = totalPrice.add(unitPrice);
  }

  return totalPrice;
}

The previous block of code tries to compute the total price of a list of products. Adding each product’s price to the total.

But then, in production, I see a NullPointerException which happens in this method.

Exception java.lang.NullPointerException: Cannot read field "intCompact" because "augend" is null
        at BigDecimal.add (BigDecimal.java:1385)
        at com.sergio.backend.BasketService.computeTotalPrice(BasketService.java:92)
...

There is a product which has no price, where the unit price is null.

I didn’t see it in the staging environment, because I always test with the same fully configured products. But in production, there are new products every week. Customers may want to buy them before they are configured.

The Solution

So, let’s see the first solution to solve the NullPointerException.

public BigDecimal computeTotalPrice(List<Product> products) {
  
  BigDecimal totalPrice = BigDecimal.ZERO;

  for (Product product : products) {

    BigDecimal unitPrice = product.getUnitPrice();

    if (unitPrice != null) {
        totalPrice = totalPrice.add(unitPrice);
    }
  }

  return totalPrice;
}

If the product has no unit price, I don’t add it to the total price.

No!

This means that a not fully configured product is free!

I don’t want to give partially configured products for free.

There is another way to solve it.

public BigDecimal computeTotalPrice(List<Product> products) {

  if (products == null) {
    return BigDecimal.ZERO;
  }
  
  BigDecimal totalPrice = BigDecimal.ZERO;

  for (Product product : products) {

    BigDecimal unitPrice = product.getUnitPrice();

    if (unitPrice == null) {
        throw new AppException("Missing price for product " + product.getId());
    }
    totalPrice = totalPrice.add(unitPrice);
  }

  return totalPrice;
}

If I want to calculate the total price of a partially configured product, the application will throw an exception.

That’s not over kill?

No. An exception is here to indicate an incorrect behavior. In this case, I’ve created a custom exception, AppException, which will be managed differently, using an Aspect.

I’ve also protected my method for another NullPointerException checking the variable products.

Why this time, do I return 0? Does it mean that it’s free?

Well, if there is no product in the basket, the price is 0.

I can avoid this validation if I have never used null in my application. I can avoid this validation if I initialize the products list with an empty list. This way, it goes to the for-loop with no problem.

Conclusion

So, the solution is replacing a NullPointerException with a custom Exception?

Pretty much.

With the NullPointerException I only see that an object is missing. With my custom exception, I know the application has a missing data. And I know which data is missing.

In fact, this shows a misconfiguration. And I can solve the problem directly in the database by adding the missing price. Or unpublishing the misconfigured product.

This means the problem is not in the code but in the data. Not all the problems in an application are related to the code. When the problem comes from the data, a custom exception must be thrown to warn about it.

My New ebook, How to Master Git With 20 Commands, is available now.

Leave a comment

A WordPress.com Website.