Improving Null Safety in Java

In this article, we will present a couple of ways to prevent writing needless null checking by using Optional API and lambda expressions.

Let’s consider this hierarchy of classes:

class Level1 {
  Level2 level2;
  // getter
}
class Level2 {
  Level3 level3;
  // getter
}
class Level3 {
  String name;
  // getter
}

Dealing with the name attribute will complex a little bit our code in the next lines if one of the parent objects is null and causing NullPointerException:

String format(Level1 level1) {
  String name = level1
    .getLevel2()
    .getLevel3()
    .getName();
    
  return name.toUpperCase();
}

So, in a first step, we decided to wrap check the name before calling .getName() method:

String format(Level1 level1) {
  String name = level1
    .getLevel2()
    .getLevel3()
    .getName();

  if (name != null)
    return name.toUpperCase();
  else
    return "Default";
}

But this will not resolve the problem because we don’t which object can or will be null. We need to check all the values before accessing the name:

String format(Level1 level1) {
  if (level1 != null 
      && level1.getLevel2() != null 
      && level1.getLevel2()
               .getLevel3() != null) {

  String name = level1
      .getLevel2()
      .getLevel3()
      .getName();
      
  if (name != null)
    return name.toUpperCase();
  else
    return "Default";
}

In this first version, we managed those null checks and the code performed everything requested but we can not cover all the cases. So we can get rid of all those checks and verbose code by using the Java Optional API.

The Optional class enables us to pipe multiple map operations in a row. Null checks are automatically handled under the hood in a declarative style:

String format(Level1 level1) {
  return Optional.ofNullable(level1)
    .map(Level1::getLevel2)
    .map(Level2::getLevel3)
    .map(Level3::getName)
    .map(s -> s.toUpperCase())
    .orElse("Default");
}

With this declarative style, the code describes the “what” to do and not “how” to do hence the brevity of the code.

Conclusion

Both solutions are good and have advantages and disadvantages like performance, readability and maintainability.