Annoying NullPointerException part1

One day, I received a text message that said, “Dear null, hello, XXX.” I laughed at that moment because it’s a joke that programmers can easily get. The program didn’t retrieve my name and instead formatted the empty space as null. Clearly, null was not handled properly. Even if it was replaced with “guest” or “customer,” it wouldn’t have caused such a joke. When a variable in a program is null, it means that it has no reference or pointer. Any operation performed on this variable will inevitably result in a null pointer exception, which is called a NullPointerException in Java. So, when and how does a null pointer exception occur, and how can it be fixed? Although null pointer exceptions can be frustrating, they are relatively easy to identify. What’s more troublesome is understanding the meaning of null. For example, when a client sends null data to a server, what is their intention? Are they providing an empty value or not providing any value at all? Another example is the NULL value in database fields. Does it have any special meaning? When dealing with NULL values in a database, what should be taken into special consideration when writing SQL? Today, let’s embark on a journey to explore the pitfalls of null with these questions in mind. NullPointerException is the most common exception in Java code, and I categorize its most likely scenarios into the following five: Parameter value is an Integer or other wrapper type, and a NullPointerException occurs due to auto-unboxing when used. NullPointerException occurs during string comparison. Containers like ConcurrentHashMap do not support null keys or values. Attempting to put a null key or value forcefully will result in a NullPointerException. Object A contains object B. After obtaining B through a field of object A, a NullPointerException occurs when invoking a method on B without checking the field for null. A List returned by a method or remote service is not empty but null. Invoking a method on the List without checking for null will result in a NullPointerException. To demonstrate these five scenarios, I have written a method called wrongMethod and another method called wrong to invoke it. The wrong method takes an input parameter test, which is a string of length 4 consisting of 0s and 1s. Each position represents whether the corresponding parameter in wrongMethod is null (1) or not null (0), allowing us to simulate various null pointer situations. private List wrongMethod(FooService fooService, Integer i, String s, String t) { log.info("result {} {} {} {}", i + 1, s.equals("OK"), s.equals(t), new ConcurrentHashMap().put(null, null)); if (fooService.getBarService().bar().equals("OK")) log.info("OK"); return null; } @GetMapping("wrong") public int wrong(@RequestParam(value = "test", defaultValue = "1111") String test) { return wrongMethod(test.charAt(0) == '1' ? null : new FooService(), test.charAt(1) == '1' ? null : 1, test.charAt(2) == '1' ? null : "OK", test.charAt(3) == '1' ? null : "OK").size(); } class FooService { @Getter private BarService barService; } class BarService { String bar() { return "OK"; } } It’s evident that this case encounters a NullPointerException because the variable is a null pointer. Trying to retrieve the value of the variable or access its members will result in a NullPointerException. However, pinpointing the exact location of this exception can be tricky. In the wrongMethod test method, we simulate four instances of a NullPointerException in a single line of code, which we log for tracking purposes. These instances are as follows: Performing the operation +1 on the input parameter Integer i. Comparing the input parameter String s to check if its content is equal to “OK.” Comparing the input parameters String s and String t to check if they are equal. Performing a put operation on a newly created ConcurrentHashMap, setting both the Key and Value as null. The output of the exception information is as follows: java.lang.NullPointerException: null at com.foo.myblog.nullvalue.controller.NullValueController.wrongMethod(NullValueController.java:41) at com.foo.myblog.nullvalue.controller.NullValueController.wrong(NullValueController.java:23) Next, let’s take a look at how to fix the five null pointer exceptions mentioned earlier. In fact, the most straightforward way to handle any null pointer exception is to check for null before performing any operations. However, this approach only prevents the exception from occurring again. We still need to determine whether the null pointer in the program logic originates from the input parameters or a bug: If it originates from the input parameters, further analysis is needed to determine if the input parameters are reasonable, among other considerations. If it originates from a bug, the null pointer may not

May 11, 2025 - 02:12
 0
Annoying NullPointerException part1

Image description

One day, I received a text message that said, “Dear null, hello, XXX.” I laughed at that moment because it’s a joke that programmers can easily get. The program didn’t retrieve my name and instead formatted the empty space as null. Clearly, null was not handled properly. Even if it was replaced with “guest” or “customer,” it wouldn’t have caused such a joke.

When a variable in a program is null, it means that it has no reference or pointer. Any operation performed on this variable will inevitably result in a null pointer exception, which is called a NullPointerException in Java. So, when and how does a null pointer exception occur, and how can it be fixed?

Although null pointer exceptions can be frustrating, they are relatively easy to identify. What’s more troublesome is understanding the meaning of null. For example, when a client sends null data to a server, what is their intention? Are they providing an empty value or not providing any value at all? Another example is the NULL value in database fields. Does it have any special meaning? When dealing with NULL values in a database, what should be taken into special consideration when writing SQL?

Today, let’s embark on a journey to explore the pitfalls of null with these questions in mind.

NullPointerException is the most common exception in Java code, and I categorize its most likely scenarios into the following five:

  1. Parameter value is an Integer or other wrapper type, and a NullPointerException occurs due to auto-unboxing when used.
  2. NullPointerException occurs during string comparison.
  3. Containers like ConcurrentHashMap do not support null keys or values. Attempting to put a null key or value forcefully will result in a NullPointerException.
  4. Object A contains object B. After obtaining B through a field of object A, a NullPointerException occurs when invoking a method on B without checking the field for null.
  5. A List returned by a method or remote service is not empty but null. Invoking a method on the List without checking for null will result in a NullPointerException.

To demonstrate these five scenarios, I have written a method called wrongMethod and another method called wrong to invoke it. The wrong method takes an input parameter test, which is a string of length 4 consisting of 0s and 1s. Each position represents whether the corresponding parameter in wrongMethod is null (1) or not null (0), allowing us to simulate various null pointer situations.

private List wrongMethod(FooService fooService, Integer i, String s, String t) {
    log.info("result {} {} {} {}", i + 1, s.equals("OK"), s.equals(t),
            new ConcurrentHashMap().put(null, null));
    if (fooService.getBarService().bar().equals("OK"))
        log.info("OK");
    return null;
}
@GetMapping("wrong")
    public int wrong(@RequestParam(value = "test", defaultValue = "1111") String test) {
        return wrongMethod(test.charAt(0) == '1' ? null : new FooService(),
                test.charAt(1) == '1' ? null : 1,
                test.charAt(2) == '1' ? null : "OK",
                test.charAt(3) == '1' ? null : "OK").size();
    }
class FooService {
        @Getter
        private BarService barService;

    }

    class BarService {
        String bar() {
            return "OK";
        }
    }

It’s evident that this case encounters a NullPointerException because the variable is a null pointer. Trying to retrieve the value of the variable or access its members will result in a NullPointerException. However, pinpointing the exact location of this exception can be tricky.

In the wrongMethod test method, we simulate four instances of a NullPointerException in a single line of code, which we log for tracking purposes. These instances are as follows:

  1. Performing the operation +1 on the input parameter Integer i.
  2. Comparing the input parameter String s to check if its content is equal to “OK.”
  3. Comparing the input parameters String s and String t to check if they are equal.
  4. Performing a put operation on a newly created ConcurrentHashMap, setting both the Key and Value as null. The output of the exception information is as follows: java.lang.NullPointerException: null at com.foo.myblog.nullvalue.controller.NullValueController.wrongMethod(NullValueController.java:41) at com.foo.myblog.nullvalue.controller.NullValueController.wrong(NullValueController.java:23) Next, let’s take a look at how to fix the five null pointer exceptions mentioned earlier.

In fact, the most straightforward way to handle any null pointer exception is to check for null before performing any operations. However, this approach only prevents the exception from occurring again. We still need to determine whether the null pointer in the program logic originates from the input parameters or a bug:

  1. If it originates from the input parameters, further analysis is needed to determine if the input parameters are reasonable, among other considerations.
  2. If it originates from a bug, the null pointer may not be solely a programming bug but could also involve business attributes and interface invocation standards. In this case, since it’s a demo, we will focus on the pure null pointer check and fix approach. When considering checking for null before processing, many people may think of using if-else blocks. However, this approach increases code complexity and reduces readability. Instead, we can try using Java 8’s Optional class to eliminate such if-else logic, enabling null checks and processing in a single line of code.

Here is the approach to fixing these issues:

  1. For checking null with Integer, you can use Optional.ofNullable to construct an Optional and then use orElse(0) to replace null with a default value before performing the +1 operation.
  2. For comparing String with a literal value, you can place the literal value first, for example, "OK".equals(s). This way, even if s is null, it will not throw a NullPointerException. For comparing two nullable String variables, you can use Objects.equals, which handles null checks.
  3. Regarding ConcurrentHashMap, since neither its Key nor Value supports null, the fix is to avoid storing null values in it. While HashMap allows null as Key and Value, ConcurrentHashMap, despite being a thread-safe version of HashMap, does not support null values for Key and Value. This can be a common misconception.
  4. In the case of cascading calls like fooService.getBarService().bar().equals("OK"), there are multiple places where null checks are required, including fooService, the return value of getBarService(), and the String returned by the bar method. Using if-else statements for null checks may require several lines of code, but using Optional allows you to handle it in a single line.
  5. For the List returned by rightMethod, since it’s uncertain whether it is null or not, you can also use Optional.ofNullable to wrap the return value and then use .orElse(Collections.emptyList()) to get an empty List if the original List is null. Finally, you can call the size method.
private List rightMethod(FooService fooService, Integer i, String s, String t) {
        log.info("result {} {} {} {}", Optional.ofNullable(i).orElse(0) + 1, "OK".equals(s), Objects.equals(s, t), new HashMap().put(null, null));
        Optional.ofNullable(fooService)
                .map(FooService::getBarService)
                .filter(barService -> "OK".equals(barService.bar()))
                .ifPresent(result -> log.info("OK"));
        return new ArrayList<>();
    }
@GetMapping("right")
    public int right(@RequestParam(value = "test", defaultValue = "1111") String test) {
        return Optional.ofNullable(rightMethod(test.charAt(0) == '1' ? null : new FooService(),
                test.charAt(1) == '1' ? null : 1,
                test.charAt(2) == '1' ? null : "OK",
                test.charAt(3) == '1' ? null : "OK"))
                .orElse(Collections.emptyList()).size();
    }

These approaches can help you fix the null pointer exceptions and enhance the robustness of your code.

This raises another question: using null checks or Optional to avoid null pointer exceptions may not always be the best solution, as the absence of null pointer exceptions may hide deeper bugs. Therefore, to effectively address null pointer exceptions, it’s crucial to analyze each case individually, identify the root cause, and then implement appropriate null checks. Handling null values is not just about checking for non-null values and proceeding with normal business flow. It also involves considering whether an exception should be thrown, setting default values, or logging relevant information when encountering null values.

Each scenario requires careful consideration of the specific requirements and behavior of your application. This case-by-case approach allows you to address the underlying issues and handle null values in a way that aligns with your application’s design and requirements.

source code can be found in here