In this tutorial, we'll continue our series on Java 14 by taking a look at Helpful NullPointerExceptions, which is a new feature introduced with this version of the JDK.
2. Traditional NullPointerExceptions
In practice, we often see or write code that chain methods in Java. But when this code throws a NullPointerException, it can become difficult to know from where the exception originates.
Let's suppose we want to find out an employee's email address:
String emailAddress = employee.getPersonalDetails().getEmailAddress().toLowerCase();
If the employee object, getPersonalDetails() or getEmailAddress() is null, the JVM throws a NullPointerException:
Exception in thread "main" java.lang.NullPointerException at com.baeldung.java14.npe.HelpfulNullPointerException.main(HelpfulNullPointerException.java:10)
What's the root cause of the exception? It's difficult to determine which variable is null without using a debugger. Moreover, the JVM will print out only the method, filename, and line number that caused the exception.
In the next section, we'll take a look at how Java 14, through JEP 358, will solve this issue.
3. Helpful NullPointerExceptions
SAP implemented Helpful NullPointerExceptions for their commercial JVM in 2006. It was proposed as an enhancement to the OpenJDK community in February 2019, and quickly after that, it became a JEP. Consequently, the feature was finished and pushed in October 2019 for the JDK 14 release.
In essence, JEP 358 aims to improve the readability of NullPointerExceptions, generated by JVM, by describing which variable is null.
JEP 358 brings a detailed NullPointerException message by describing the null variable, alongside the method, filename, and line number. It works by analyzing the program's bytecode instructions. Therefore, it's capable of determining precisely which variable or expression was null.
Most importantly, the detailed exception message is switched off by default in JDK 14. To enable it, we need to use the command-line option:
3.1. Detailed Exception Message
Let's consider running the code again with the ShowCodeDetailsInExceptionMessages flag activated:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.toLowerCase()" because the return value of "com.baeldung.java14.npe.HelpfulNullPointerException$PersonalDetails.getEmailAddress()" is null at com.baeldung.java14.npe.HelpfulNullPointerException.main(HelpfulNullPointerException.java:10)
This time, from the additional information, we know that the missing email address of the employee's personal details causes our exception. The knowledge gained from this enhancement can save us time during debugging.
JVM composes the detailed exception message from two parts. The first part represents the failing operation, a consequence of a reference being null, while the second part identifies the reason for the null reference:
Cannot invoke "String.toLowerCase()" because the return value of "getEmailAddress()" is null
To build the exception message, JEP 358 recreates the part of the source code that pushed the null reference onto the operand stack.
3.2. Technical Aspects
Now that we have a good understanding of how to identify null references using Helpful NullPointerExceptions, let's take a look at some technical aspects of it.
Firstly, a detailed message computation is only done when the JVM itself throws a NullPointerException — the computation won't be performed if we explicitly throw the exception in our Java code. The reason behind this is that, in these situations, most probably we already pass a meaningful message in the exception constructor.
Secondly, JEP 358 calculates the message lazily, meaning only when we print the exception message and not when the exception occurs. As a result, there shouldn't be any performance impact for the usual JVM flows, where we catch and rethrow exceptions, since we don't always print the exception message.
Finally, the detailed exception message may include local variable names from our source code. Thus, we could consider this a potential security risk. However, this only happens when we run code that was compiled with the -g flag activated, which generates and adds debug information into our class file.
Consider a simple example that we've compiled to include this additional debug information:
Employee employee = null; employee.getName();
When we run this code, the exception message prints the local variable name:
Cannot invoke "com.baeldung.java14.npe.HelpfulNullPointerException$Employee.getName()" because "employee" is null
In contrast, without additional debug information, the JVM provides only what it knows about the variable in the detailed message:
Cannot invoke "com.baeldung.java14.npe.HelpfulNullPointerException$Employee.getName()" because "<local1>" is null
Instead of the local variable name (employee), the JVM prints the variable index assigned by the compiler.
In this quick tutorial, we learned about Helpful NullPointerExceptions in Java 14. As shown above, improved messages help us to debug code faster due to the source code details present in the exception messages.
As always, the full source code of the article is available over on GitHub.