If you program in Java, you're bound to be familiar with the exception given in the title. Although fixing it is usually easy, the key to success is to avoid the exception before it happens.
A seemingly simple implementation
Suppose we have a user's id data, then we want to retrieve the user (from some database/user repository) and display the user's name in the console. Sound trivial? The first implementation could look like this:
1User user = findUser(userId); System.out.println(user.getName());
Where the findUser method would look like this:
1public User findUser() { return users.get(userId); }
At first glance, everything looks fine. However, what if we get an id that is not in the database? Well, because the findUser method must return an object of type User, so it will return null. You will probably find that it is enough to add a simple check:
1User user = findUser(userId); if (user != null) { System.out.println(user.getName()); }
Actually, it will take care of the issue. However, let's consider... why did we add this check? It happened because we received a NullPointerException. It's just that we would prefer to avoid its occurrence at all. We could try to add a check for every possible variable in the project, but then such checks would make up half of our code, and we don't want that after all.
Optional
With help comes Optional, a class from the not-so-new Java 8. It is a kind of "wrapper" for a variable that may (but does not have to) be a null. So let's change the implementation of the findUser method:
1public Optional<User> findUser(Long userId) { return Optional.ofNullable(users.get(userId)); }
As we can see, we will wrap the User class in the Optional class. The key point here is that the returned type in the method has changed - we have changed the API. The usage can look as follows:
1findUser(userId) .ifPresent(u -> System.out.println(u.getName()));
We have added a more functional character to our code, while what is most important is that now we are not physically able to forget to handle a null - the compiler simply won't allow us to do so.
Useful Optionala methods
In addition to just checking the existence of a given object, we have a rich API at our disposal. Suppose we have a User class with the following fields:
1String name; int age; List<String> roles
Then we would like, based on objects of type User, to extract its name, but only under the condition that the user is an admin and is an adult. An example method that meets these requirements could look like this:
1public String extractAdultAdminName(User user) { return Optional.ofNullable(user) .filter(u -> u.getAge() >= 18) .filter(u -> u.getRoles().contains("ADMIN")) .map(u -> u.getName()) .orElse(""); }
Let's discuss the above example one by one: First, we want to wrap the method argument in Optionala, since we don't know if someone won't pass us a null. Next, the filter(...) and map(...) methods will only execute if Optional is "full". Otherwise, they will be skipped. The filter(...) method, as the name implies, filters the Optional's interior - we will move on if the object sitting inside, meets the given condition. The map(...) method, on the other hand, maps an object to anything else. In the above case, we'll turn a User object into a String. Finally, the orElse(...) method will return the inner object (if it's been there so far), or otherwise, whatever we specify.
That would be almost right, while note that with the above implementation, the absence of an adult admin will result in the return of an empty String. So, at the point of calling this method, we would have to check again that the result is not an empty String. Easy to overlook. So let's improve the above method to the final implementation:
1public Optional<String> extractAdultAdminName(User user) { return Optional.ofNullable(user) .filter(u -> u.getAge() >= 18) .filter(u -> u.getRoles().contains("ADMIN")) .map(u -> u.getName()); }
This way, whoever calls our method will know that they also have to handle the situation when the adult admin is missing.
Summary
If we know that our method can return a null, then let's try to replace the returned type with Optionala. That way later, when using the above method anywhere, we can be sure that we won't forget to handle the null (checking at compile time, not at runtime), and NullPointerException won't appear (or at least not because of it).