6 Tips to Avoid NullPointerException in Java

keremmican
Javarevisited
Published in
7 min readJul 4, 2024
Photo by Kelly Sikkema on Unsplash

One of the most common errors faced by Java programmers is the NullPointerException, which can cause applications to crash unexpectedly and negatively impact the user experience.

NullPointerException is a class in the java.lang package that extends RuntimeException. The primary cause of this error is attempting to access null references. Identifying and preventing such errors allows developers to make their code more reliable and maintainable. In this article, we will explore effective ways to avoid NullPointerException and provide some practical tips to help you prevent this error.

These examples are written with the goal of “advancing without getting a NullPointer through code.” In professional life, especially in large and complex services, we may have to follow this logic. I have tried to compile the information I needed based on my own experiences here, and I hope you find it useful. Enjoy the article!

Why do we encounter it frequently?

In Java, the default value for reference type variables is null.

When we declare an object at the beginning of a method or class without instantiating it, its default value will be null. Attempting to use these objects makes it inevitable to encounter a NullPointerException.

public class Example {
private String name;

public static void main(String[] args) {
Example example = new Example();
System.out.println(name.length());
//NullPointerException occurs because the name variable is null
}
}

The fields of an object can be null.

Some fields of a newly defined or returned object from a method may be null, and we may encounter this error when performing operations with them.

For example, let’s assume we have a User object and mimic retrieving it from a database:

class User {
private String username;
private String email;

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}
}

In the function call below, only the username field is assigned a value, while the email field remains null. Later, we attempt to retrieve this email field and use the length() function of the String class to get the length of the email. However, since this field is null, the operation results in an error.

public class DatabaseExample {

public static void main(String[] args) {
User user = getUserFromDatabase();
System.out.println(user.getEmail().length());
//NullPointerException, email might be null
}

public static User getUserFromDatabase() {
User user = new User();
user.setUsername("keremmican");
return user;
}
}

Insufficient checks and hastily written code.

In some of our methods, there may be many checks and loops that distract us and that we might overlook. When we write code quickly and under stress, we often skip some checks. Thinking about edge cases and trying to perform comprehensive unit tests minimizes potential errors.

Besides commonly encountered examples, there are also cases such as using null objects in the synchronized keyword parameter or passing them as method arguments.

How do we prevent it?

While coding, there are situations where we might encounter a NullPointerException and want to handle it accordingly. In such cases, the try-catch mechanism in Java comes into play. However, in this article, we are exploring solutions aimed at continuing our code without encountering the NullPointerException error at all.

1. Simple If-Else Check

This is the most basic error prevention mechanism. By checking if the variable is null, we perform operations only if it is not.

TestObj obj = null;

if (obj != null) { //do the job if not null
System.out.println(obj .getTestField());
} else {
//exception handling or the job if null
}

2. Correct Usage of Equals Method

When checking the value of a variable that can be null, we compare it to a constant value using the equals method. The key point here is to call the equals method on an object that is guaranteed not to be null. Otherwise, if we call it on an object that could potentially be null, we still risk encountering an error.

String str = null;

if ("test".equals(str)) {
System.out.println("String is equal to 'test'.");
} else {
System.out.println("String is null or not equal to 'test'.");
}

In this example, since the “test” String is not a variable, it is considered safe to call our equals method on this object. If we had called the equals method on the str variable to check if it is equal to “test”, we would risk encountering a NullPointerException.

3. Java 8 and Beyond: Usage of Optional

The Optional class introduced in Java 8 wraps a potentially nullable object, enabling safer and more readable code writing.

String str = null;

Optional<String> optionalStr = Optional.ofNullable(str);

optionalStr.ifPresent(s -> System.out.println("Length: " + s.length()));

In this example, the Optional class’s method `ofNullable` indicates that the `optionalStr` object could be null, prompting us to perform necessary checks when operating on this object throughout the rest of the code. This approach minimizes the risk of skipping checks and helps prevent potential errors.

If we had attempted to directly retrieve the object using `optionalStr.get()` without using `ifPresent`, IDE would warn us with the following message, making a NullPointerException inevitable:

“Call `optional.isPresent()` or `!optional.isEmpty()` before accessing the value.”

4. Usage of `String.valueOf()` Method

Although this function internally checks for null objects, it reduces boilerplate code. If you want to avoid NullPointerException in a specific scenario, using `String.valueOf()` will make your code safer according to your requirements.

Let’s proceed with an example in Java where we write to an external file. Assume we have a list containing strings, and we want to write each element of this list to a file named `output.txt` line by line.

public static void main(String[] args) {
List<String> strList = Arrays.asList("1", null, "2");
String filePath = "C:\\Users\\keremmican\\output.txt";

try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
for (String str : strList) {
writer.write(str);
writer.newLine();
}
System.out.println("The file has been successfully written.");
} catch (IOException e) {
System.err.println("File writing error: " + e.getMessage());
}
}

When you call the write method of BufferedWriter to write a String, it actually determines the number of characters to write by calling the length() method. In this case, if you attempt to perform an operation like null.length(), it tries to invoke length() on null, resulting in a NullPointerException.

//writer.write(str);
writer.write(String.valueOf(str));

If we make the change as described above, the likelihood of encountering an error in this function significantly decreases. The String.valueOf() function takes a value and internally performs a null check; if the value is null, it returns the string “null”.

Another example:

We have a list of String objects and we want to insert an Integer object into it.

List<String> strList = new ArrayList<>();

Integer num = null;

strList.add(num.toString());

However, since our `num` object is null, calling the `toString()` method will definitely result in an error. To avoid a NullPointerException, the `valueOf()` method of the String class can be used.

strList.add(String.valueOf(num));

As a result, our `num` object, which was null, was added to our list as the String “null”. While it may seem meaningless, we have prevented the error.

Making a habit of using such functions that reduce code complexity and increase readability will be beneficial for a developer in every aspect.

For example, using `Arrays.asList(…)` instead of `List.of(…)` can also prevent NullPointerException errors. There are many such functions that can be listed.

5. Usage of Objects and ObjectUtils

Functions from the Objects and ObjectUtils classes actually perform the same tasks as simple if-else checks, but using them is more professional and enhances code readability and maintenance.

For instance, let’s say we have a list that holds user preferences, and we treat it as if we’re fetching it from a service or database. Assume we don’t want any null elements in this list.

List<String> list = new ArrayList<>();
list.add("custom1");
list.add("custom2");

String preference = getUserPreference();

list.add(Objects.requireNonNullElse(preference, "default"));

Objects.requireNonNullElse() method takes our value that might be null and uses the second parameter as a default value if the first one is null. This ensures that no null enters the list.

ObjectUtils.defaultIfNull() method works similarly and belongs to the Apache Commons Lang library. This library is reliable, but you can also prefer the Objects class from the java.util package.

6. Use Primitive Types

Yes, that’s correct. Use primitive types if you can because they can’t be null. Also they take up little space. So don’t forget them.

Bonus: Annotations and Bean Validation

Annotations are important helpers for those working with the Spring Framework, enabling null checks and making our methods safer.

The Bean Validation API in Java is used to validate the validity of object properties. This API ensures that objects comply with specific rules (constraints). Constraints are usually applied at the class level and are specified through annotations. One commonly used constraint for null checks is the @NotNull annotation.

For example, let’s say we have an endpoint where we send a request with an Example object. To activate the @NotNull annotation, we would add the @Valid annotation at the beginning of our parameter.

public class TestController {

@PostMapping("/test")
public void test(@Valid @RequestBody Example example) {
...
}
}

@Data
class Example {
@NotNull
private String str;
}

When we send a request to this endpoint with an empty body, we will receive a MethodArgumentNotValidException, accompanied by the message “[str]]; default message [must not be null]]”. This way, null validation will occur before the object reaches the method levels, and if a field that we do not want to be null is null, its passage will be denied.

Additionally, @NotBlank and @NotEmpty can be used for String values.

Conclusion

In this article, we explored various methods to prevent NullPointerExceptions, starting from simple null checks to advanced techniques such as Java 8’s Optional class and Bean Validation. Each method was crucial in ensuring the code is more reliable and maintainable. By implementing these strategies, you can minimize errors in your Java projects and build a more robust codebase. This will ultimately help in developing applications that are easier to maintain in the long term.

You can also read this article from my website.

If you liked this content, you can support me in the link below!

--

--