Java Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

> CHECK OUT THE COURSE

1. Introduction

In this tutorial, we'll learn four approaches to check if all variables of an object are null.

2. Why Check if Variables are Null

The null value in Java means the absence of a variable's value. Technically, a variable containing null doesn't point to any position in memory or wasn't initialized yet. That can only occur with instance variables. Primitive variables such as int, double, and boolean can't hold null.

Checking for null variables in our programs is helpful to avoid unexpected errors like IllegalArgumentException or a NullPointerException. When we try to access any member (field or method) of a null object, Java throws a NullPointerException.

One common scenario is instantiating a new object containing nested objects. The nested object is not initialized automatically. Thus, if we try to access the members of that nested object, we'll get an unexpected error at runtime. To avoid scenarios like that, we can check if all variables in a class are null before using its members.

To exemplify, we'll use the Car class defined below:

public class Car {
    Integer power;
    Integer year;
}

3. Using if  Statements

The simplest way is using a sequence of if statements to check field by field if they are null or not and, in the end, return a combination of their results. Let's define the allNull() method inside the Car class:

public boolean allNull() {     
    if (power != null) {
        return false;
    }
        
    if (year != null) {
        return false;
    }
        
    return true;
}

The method defined above works just fine, as shown in the test below:

@Test
public void givenNullFields_whenCheckForNullsUsingIfs_thenReturnCorrectValue(){
    Car car = new Car();
    boolean result = car.allNull();
    assertTrue(result);
}

Although simple, this approach can be considered a code smell because we need to add a new if statement for each new field in our class. This might look effortless at first, but imagine an application with dozens of classes that contain dozens of fields. In that case, we need to write a method that validates every field for each class. That's not very maintainable. Use this approach only if you have small classes that are less likely to change over time.

4. Using the Stream Class

We can refine the if statement solution to something more declarative using the Stream API. For example, instead of having a boolean clause for every field in our class, we can use the Stream's allMatch() and Objects's isNull() methods together. Let's see how it works by creating another method in the Car class like the following:

public boolean allNullV2() {
    return Stream.of(power, year)
      .allMatch(Objects::isNull);
}

That solution also works fine, as demonstrated in the following unit test:

@Test
public void givenNullFields_whenCheckForNullsUsingStreams_thenReturnCorrectValue(){
    Car car = new Car();
    boolean result = car.allNullV2();
    assertTrue(result);
}

The allNullV2() is the declarative version of allNull(). It makes the code more extensible since we need to add only the field name in the Stream instead of an entire if statement.

5. Using ObjectUtils from Apache Commons

Another option is to use the utility class ObjectUtils from the Apache commons-lang3 libraryThe ObjectUtils's allNull() method has a generic API that handles any type and number of parameters. That method receives an array of Objects and returns true if all values in that array are null. Otherwise, return false. Let's first add the latest version of the commons-lang3 dependency to our pom.xml file:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

Now, let's see how the allNull() method works in the following unit test:

@Test
public void givenNullFields_whenCheckForNullsUsingApacheCommons_thenReturnCorrectValue(){
    Car car = new Car();      
    boolean result = ObjectUtils.allNull(car.power, car.year);
    assertTrue(result);
}

That approach uses an external dependency to achieve the same result. So, we must consider if it's worth pulling an entire dependency for such a simple task. Similar to the Stream approach, the good thing about allNull() is that it receives an array of Objects, creating a generic API. Thus, that works with any type and number of fields in our class. However, we still need to pass newly added class fields as arguments for the allNull() method.

We'll see in the next section that, using the Reflection API, we can avoid modifying our null checker whenever we add more variables to our model class.

6. Using the Reflection API

Since all fields inherit the Object class, we can create a generic method that works with any type except primitives. To do that, we can access all fields of an Object's instance in runtime and search for nulls using the Reflection API. Let's create a new class named NullChecker with the content below:

public class NullChecker {

    public static boolean allNull(Object target) {
        return Arrays.stream(target.getClass()
          .getDeclaredFields())
          .peek(f -> f.setAccessible(true))
          .map(f -> getFieldValue(f, target))
          .allMatch(Objects::isNull);
    }

    private static Object getFieldValue(Field field, Object target) {
        try {
            return field.get(target);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

This version is slightly more complicated than the previous ones, so let's analyze that code in parts:

  • The allNull() method defines a Stream of the target Object's declared fields using the getClass() and getDeclaredFields() methods. In our example, that Stream consists of power and year fields
  • The peek() operation makes all private fields (if any) accessible from outside the class using the setAcessible(true) method
  • Then, we map() the class fields to their values using the helper method getFieldValue(), which catches the checked exception thrown by the get() method of the Field class
  • Finally, we check if all class fields values are null using allMatch()

Let's validate if that works using a unit test:

@Test
public void givenNullFields_whenCheckForNullsUsingReflection_thenReturnCorrectValue() {
    Car car = new Car();
    boolean result = NullChecker.allNull(car);
    assertTrue(result);
}

That API is generic in its contract and works with any class we define. However, the Reflection API has the pitfall of being more dangerous than the other approaches because we don't need any prior knowledge about our null checker at compile time, which makes bugs harder to find. Another problem with Reflection is that it is usually slower than other approaches since there's much more work to be done at runtime.

7. Conclusion

In this article, we've seen the importance of checking for null variables in our classes and how to do that using if statements, Streams, the Apache commons-lang3 library, and the Reflection API. As usual, the source code is available over on GitHub.

Java bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Generic footer banner
guest
0 Comments
Inline Feedbacks
View all comments