The NullPointerException Nightmare: What Options Do We Have?

Java is a beautiful language, until you start to get NullPointerException everywhere. That is one of the most annoying exceptions we can get meanwhile we code.

In this post, we are going to talk about how null could be a nightmare, what we usually do to avoid problems, how the Null Object pattern could help us and finally, the Optional class to improve our solution.

TRY IT YOURSELF: You can find this post source code here.

Finding an Entity by ID

Well, let’s see an example about finding an entity by id. Let’s start with the Repository interface:

interface Repository {
  User findById(String id);

  List<String> findAllIds();
}

Next, let’s see our User class:

class User {
  private final String id;
  private final String name;

  public User(String id, String name) {
    this.id = id;
    this.name = name;
  }

  public String getId() {
    return id;
  }

  public String getName() {
    return name;
  }
}

Now, let’s see our Service logic:

package com.coderstower.blog....;

import java.util.List;

class Service {
  private final Repository repository;

  public Service(Repository repository) {
    this.repository = repository;
  }

  public User getByName(String name) {
    List<String> users = this.repository.findAllIds();
    
    for (String id : users) {
      User userComplete = this.repository.findById(id);
      
      if (userComplete.getName().equals(name)) {
        return userComplete;
      }
    }
    return null;
  }
}

getByName is going to find all the users, iterate over them, find by id more details and search one with the name we want.

Well, let’s see some unit tests about this class:

@RunWith(MockitoJUnitRunner.class)
public class ServiceTest {
  @Mock
  private Repository repository;
  @InjectMocks
  private Service service;

  @Test
  public void getByName_nullList_nullPointerException() {
    when(repository.findAllIds()).thenReturn(null);

    service.getByName("name");
  }

  @Test
  public void getByName_nullUser_nullPointerException() {
    when(repository.findAllIds())
            .thenReturn(Arrays.asList("1", "2"));
    when(repository.findById("1")).thenReturn(null);

    service.getByName("name");
  }
}

We are testing how our Service class behaves when it receives a null list or a null User, this is the result:

Unit tests failed by NullPointerException

As we can see, we didn’t prepare our Service class to support null results and it blows up in lines 15 and 18.

NOTE: You can add @Test(expected = NullPointerException.class) to get the tests passing.

Now, let’s play defensive.

Adding null validations

Well, the first approach we can apply is validating in our Service class where the result of some operation would be null:

  ...

  public User getByName(String name) {
    List<String> users = this.repository.findAllIds();
    
    if (users != null) {
      for (String id : users) {
        User userComplete = this.repository
                .findById(id);
        
        if (userComplete != null && userComplete
                .getName().equals(name)) {
          return userComplete;
        }
      }
    }
    return null;
  }

  ...

NOTE: You might think, what about the repository object and the id of the user, could them be null? Well, yes, they could, however, if you validate everything, it’s going to be a nightmare, you should have some invariant in your classes, so, you know that some variables “never” should be null. For instance, you can validate in the Service class constructor that Repository cannot be null.

Now, let’s modify a little bit our unit tests to check those validations:


@RunWith(MockitoJUnitRunner.class)
public class ServiceTest {
  @Mock
  private Repository repository;
  @InjectMocks
  private Service service;

  @Test
  public void getByName_nullList_nullPointerException() {
    when(repository.findAllIds()).thenReturn(null);
    
    User user = service.getByName("name");
    
    assertThat(user).isNull();
  }

  @Test
  public void getByName_nullUser_nullPointerException() {
    when(repository.findAllIds())
            .thenReturn(Arrays.asList("1", "2"));
    when(repository.findById("1")).thenReturn(null);
    
    User user = service.getByName("name");
    
    assertThat(user).isNull();
  }
}

Let’s run them:

Run unit tests with validations

Now, everything is good, we don’t have any NullPointerException anymore, however, our code looks messy.

Let’s improve it using the Null Object pattern.

Using the Null Object pattern

The Null Object pattern is responsible of handling default state and behavior of objects.

Null Object pattern

As we can see, we have an object which needs a default (null) behavior if there is not other implementation we can use. That null object does nothing (behavior) and its state is a default.

NOTE: Do nothing implies there is not side effect for the client to use that default behavior/state.

So, we are going to extend our solution to use this pattern:

Implementing the Null Object pattern

NOTE: User doesn’t have behavior, only state, so, the Null Object is only defining default state.

Let’s see our NullUser class:

class NullUser extends User{
    static final NullUser INSTANCE = new NullUser();

    public NullUser() {
        super("null", "null");
    }
}

We defined as default state, the id and name as null strings, besides, we created a unique INSTANCE to use everywhere.

Now, we modify our Service class to support the pattern:

  ...

  public User getByName(String name) {
    List<String> users = this.repository.findAllIds();
    
    for (String id : users) {
      User userComplete = this.repository.findById(id);
      
      if (userComplete.getName().equals(name)) {
        return userComplete;
      }
    }
    return NullUser.INSTANCE;
  }

  ...

As we can see, there are no null validation in our code, just returning the NullUser object.

NOTE: What if we send a name “null” to search for? well, yes, the comparison with the null object is going to be true, so, try to setup your null object with something out of your business context.

What about the findAll result? that is not a User, it is a list of User. Well, let’s see our unit tests:

@RunWith(MockitoJUnitRunner.class)
public class ServiceTest {
  @Mock
  private Repository repository;
  @InjectMocks
  private Service service;

  @Test
  public void getByName_nullList_nullPointerException() {
    when(repository.findAllIds())
            .thenReturn(Collections.emptyList());
    
    User user = service.getByName("name");
    
    assertThat(user).isEqualTo(NullUser.INSTANCE);
  }

  @Test
  public void getByName_nullUser_nullPointerException() {
    when(repository.findAllIds())
            .thenReturn(Arrays.asList("1", "2"));
    when(repository.findById("1"))
            .thenReturn(NullUser.INSTANCE);
    when(repository.findById("2"))
            .thenReturn(NullUser.INSTANCE);
    
    User user = service.getByName("name");
    
    assertThat(user).isEqualTo(NullUser.INSTANCE);
  }
}

As we can see the line 10, the null object of a list is an empty list, so, we don’t need to create something else. Moreover, we are using the NullUser as a null value.

Now, Java 8 introduces something pretty interesting, the Optional class.

Optional to The Rescue

The Optional class is a generic null handler, that means, it manages the null state of any object we want.

CAREFUL: Optional doesn’t apply the complete Null Object pattern, it handles only state, not behavior. The Null Object pattern is useful with state and behavior. Here, we used that pattern with the User state, not with behavior.

Now, let’s update our Repository interface to support Optional:

interface Repository {
  Optional<User> findById(String id);

  List<String> findAllIds();
}

Next, update the Service class:

  ...

  public Optional<User> getByName(String name) {
    List<String> users = this.repository.findAllIds();
    
    for (String id : users) {
      Optional<User> userComplete = this.repository
              .findById(id)
              .filter(u -> u.getName().equals(name));
      
      if (userComplete.isPresent()) {
        return userComplete;
      }
    }
    
    return Optional.empty();
  }

  ...

As we can see, this code is cleaner, we can create empty Optionals and validate if the Optional has content or not.

NOTE: See the filter method in line 9? That’s an Optional feature to filter a Optional value, so, if condition doesn’t match, the filter method returns a Optional.empty()

Let’s modify our unit tests:

@RunWith(MockitoJUnitRunner.class)
public class ServiceTest {
  @Mock
  private Repository repository;
  @InjectMocks
  private Service service;

  @Test
  public void getByName_nullList_nullPointerException() {
    when(repository.findAllIds())
            .thenReturn(Collections.emptyList());
    
    Optional<User> user = service.getByName("name");
    
    assertThat(user.isPresent()).isFalse();
  }

  @Test
  public void getByName_nullUser_nullPointerException() {
    when(repository.findAllIds())
            .thenReturn(Arrays.asList("1", "2"));
    when(repository.findById("1"))
            .thenReturn(Optional.empty());
    when(repository.findById("2"))
            .thenReturn(Optional.empty());
    
    Optional<User> user = service.getByName("name");
    
    assertThat(user).isEqualTo(Optional.empty());
  }
}

Now, we are using the Optional.empty() as a null object for the User.

NOTE: Optional class is available from Java 8. If you need something similar in lower JDKs, you can use Guava.

Final Thoughts

Null Object pattern is great to handle state and behavior, however, Optional is more generic when we talk about state.

So, I recommend using Optional to handle null state, and Null Object pattern to handle default behavior.

Advertisements

2 comments

  1. ` if (userComplete.isPresent()) ` is not that much different from checking nulls, and it can be improve either by using Optional flatMap and ifPresent or with something like this

    “`
    public Optional getByName(String name) {
    return this.repository.findAllIds()
    .stream().map(this.repository::findById)
    .flatMap(o -> o.isPresent() ? Stream.of(o.get()) : Stream.empty())
    .filter(u -> u.getName().equals(name))
    .findFirst();

    }
    “`

    Like

    • There is a difference, userComplete.isPresent() is pretty clear in its intend, userComplete == null, is not. Besides, are you sure you want to change userComplete.isPresent() with that whole functional and Stream mess? which one seems to be more easy to understand? that is a personal decision.

      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 )

Google photo

You are commenting using your Google 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