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:

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:

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.

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:

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.
If you liked this post and are interested in hearing more about my journey as a Software Engineer, you can follow me on Twitter and travel together.
` 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();
}
“`
LikeLike
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.
LikeLike