Saturday, October 26, 2019

Better NPE Messages in JDK 14

My March 2019 blog post "Better Default NullPointerException Messages Coming to Java?" was written when the draft JEP for better NullPointerException messages had not yet been targeted to a particular JDK release. Since then, that draft JEP became JEP 358 ("Helpful NullPointerExceptions"), which has been targeted for JDK 14. Even better, the initial implementation (JDK-8218628) is already in the JDK 14 branch and is available to play with in JDK 14 Early Access Builds Build 20 (2019/10/23).

In this post, I will run the example code introduced in my previous post against the JDK 14 Early Access Build 20 to demonstrate the additional details now provided. To see that example code that was written to intentionally introduce a variety of situations resulting in NullPointerExceptions, see that earlier post or view the source code on GitHub.

With the JDK 14 Early Access Build 20 downloaded and pointed to by my path, I see the following when I run java -version:

openjdk version "14-ea" 2020-03-17
OpenJDK Runtime Environment (build 14-ea+20-879)
OpenJDK 64-Bit Server VM (build 14-ea+20-879, mixed mode, sharing)

With the JDK 14 Early Access Build 20 configured appropriately, I rebuilt the source code mentioned previously and then re-ran the code with the java launcher without any new options. The output from this (shown below) is not materially different from the output with previous JDK versions.

=========================================
| #1: Element [0] on null boolean array |
=========================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateFirstExampleIndexAccessOnNullBooleanArray(Unknown Source)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

=================================
| #2: .length on null boolean[] |
=================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateSecondExampleLengthOnNullBooleanArray(Unknown Source)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

=======================================
| #3: Assigning float to null float[] |
=======================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateThirdExampleAssigningValueToElementOfNullFloatArray(Unknown Source)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

======================================
| #4: Accessing field on null object |
======================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateFourthExampleAccessInstanceFieldOfNullObject(Unknown Source)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

===================
| #5: throw null; |
===================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateFifthExampleThrowingConstantNull(Unknown Source)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

================================================
| #6: Method invocation on null instance field |
================================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateSixthExampleMethodInvocationOnNullInstanceField(Unknown Source)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

=============================================
| #7: synchronized() on null instance field |
=============================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateSeventhExampleSynchronizedNullInstanceField(Unknown Source)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

==========================================================================
| >>> Null Lost in Long Series of Method Invocations in Single Statement |
==========================================================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateNullLostInSeriesOfMethodInvocationsInSingleStatement(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

=======================================================
| >>> Null Lost in Dereferenced Constructor Arguments |
=======================================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateNullLostInConstructorAcceptingMultiplePotentiallyNullArgumentsDereferenced(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

==================================================
| >>> Null Lost in Dereferenced Method Arguments |
==================================================

java.lang.NullPointerException
 at dustin.examples.npe.NpeDemo.demonstrateNullLostInMethodAcceptingMultiplePotentiallyNullArgumentsDereferenced(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

As the above output demonstrates, even with the new JDK 14 Early Access Build 20, I don't see any new detailed information regarding NullPointerExceptions when I run my application as normal. I included this output to show that a special flag is needed to enable the more detailed NullPointerExceptions and to make it more convenient to compare the output without and with the extra details. The next output listing shows the additional details provided when the java launcher is passed the flag -XX:+ShowCodeDetailsInExceptionMessages:

=========================================
| #1: Element [0] on null boolean array |
=========================================

java.lang.NullPointerException: Cannot load from byte/boolean array because "<local1>" is null
 at dustin.examples.npe.NpeDemo.demonstrateFirstExampleIndexAccessOnNullBooleanArray(Unknown Source)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

=================================
| #2: .length on null boolean[] |
=================================

java.lang.NullPointerException: Cannot read the array length because "<local1>" is null
 at dustin.examples.npe.NpeDemo.demonstrateSecondExampleLengthOnNullBooleanArray(Unknown Source)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

=======================================
| #3: Assigning float to null float[] |
=======================================

java.lang.NullPointerException: Cannot store to float array because "<local1>" is null
 at dustin.examples.npe.NpeDemo.demonstrateThirdExampleAssigningValueToElementOfNullFloatArray(Unknown Source)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

======================================
| #4: Accessing field on null object |
======================================

java.lang.NullPointerException: Cannot read field "nullInstanceField" because "<local1>" is null
 at dustin.examples.npe.NpeDemo.demonstrateFourthExampleAccessInstanceFieldOfNullObject(Unknown Source)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

===================
| #5: throw null; |
===================

java.lang.NullPointerException: Cannot throw exception because "null" is null
 at dustin.examples.npe.NpeDemo.demonstrateFifthExampleThrowingConstantNull(Unknown Source)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

================================================
| #6: Method invocation on null instance field |
================================================

java.lang.NullPointerException: Cannot invoke "String.isEmpty()" because "this.nullInstanceField" is null
 at dustin.examples.npe.NpeDemo.demonstrateSixthExampleMethodInvocationOnNullInstanceField(Unknown Source)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

=============================================
| #7: synchronized() on null instance field |
=============================================

java.lang.NullPointerException: Cannot enter synchronized block because "this.nullInstanceField" is null
 at dustin.examples.npe.NpeDemo.demonstrateSeventhExampleSynchronizedNullInstanceField(Unknown Source)
 at dustin.examples.npe.NpeDemo.demonstrateJdk8218628Examples(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

==========================================================================
| >>> Null Lost in Long Series of Method Invocations in Single Statement |
==========================================================================

java.lang.NullPointerException: Cannot invoke "dustin.examples.npe.DysfunctionalLocation$Province.getCity()" because the return value of "dustin.examples.npe.DysfunctionalLocation$Nation.getProvince()" is null
 at dustin.examples.npe.NpeDemo.demonstrateNullLostInSeriesOfMethodInvocationsInSingleStatement(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

=======================================================
| >>> Null Lost in Dereferenced Constructor Arguments |
=======================================================

java.lang.NullPointerException: Cannot invoke "java.lang.Long.longValue()" because "<local6>" is null
 at dustin.examples.npe.NpeDemo.demonstrateNullLostInConstructorAcceptingMultiplePotentiallyNullArgumentsDereferenced(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

==================================================
| >>> Null Lost in Dereferenced Method Arguments |
==================================================

java.lang.NullPointerException: Cannot invoke "java.lang.Long.longValue()" because "<local6>" is null
 at dustin.examples.npe.NpeDemo.demonstrateNullLostInMethodAcceptingMultiplePotentiallyNullArgumentsDereferenced(Unknown Source)
 at dustin.examples.npe.NpeDemo.main(Unknown Source)

JEP 358 explains the use of this flag for seeing additional NullPointerException details: "The feature can be toggled with the new boolean command-line option -XX:{+|-}ShowCodeDetailsInExceptionMessages. The option will first have default 'false' so that the message is not printed. It is intended to enable code details in exception messages by default in a later release." As we see, this feature is initially turned off by default, but there is a plan to enable the more detailed NullPointerException messages in the future.

A recent Tweet asked the question, "How it will work if bytecode doesn’t contain variable names?" The question continued by providing a specific example: "Suppose we have code like Object a = ....; a.getName(); //NPE What kinds of message NPE would have?" Although an example of this is included in my battery of tests shown earlier, I thought I'd provide a more focused example here in response to that question. The next code listing (which is also available on GitHub) shows code adapted from the example used in the Tweet.

package dustin.examples.npe;

/**
 * Simple demonstration to answer Tweet-ed question
 * "How it will work if bytecode doesn't contain variable names?"
 * (https://twitter.com/2doublewhiskey/status/1180365953240055809).
 */
public class TwoDoubleWhiskeyTweetExample
{
   public static void main(final String[] arguments)
   {
      final Person person = null;
      person.getName(); //NPE
   }
   
   public static class Person
   {
      private String name;

      public Person(final String newName)
      {
         name = newName;
      }

      public String getName()
      {
         return name;
      }
   }
}

The next screen snapshot shows the result of running this simple application with the JDK 14 Early Access Build 20 without and then with the java launcher flag -XX:+ShowCodeDetailsInExceptionMessages.

As the screen snapshot indicates, using the -XX:+ShowCodeDetailsInExceptionMessages flag with the JDK 14 Early Access Build 20 provides this additional detail related to this simple NullPointerException example: "Cannot invoke "dustin.examples.npe.TwoDoubleWhiskeyTweetExample$Person.getName()" because "<local1>" is null"

An example that is simpler and even closer to the original example provided in the Tweet-ed question is available on GitHub.

JEP 358 ("Helpful NullPointerExceptions") may not be as flashy as some other JEPs that come to new JDK releases, but it may be one that in the end provides more value to Java developers on a daily basis than some of its flashier peers. There are numerous examples where this will be helpful and many of those example situations are spelled out in the JEP itself and in my code examples referenced in this post.