What is one of the biggest shortcomings in standard exception reporting?
Warning: the following string of characters is known to induce stress responses in certain individuals of the human species. “java.lang.NullPointerException: null”.
If you’ve ever been frustrated with an exception, you’ve reached the right place.
In this post, we’d like to highlight one of the shortcomings in the standard way Java reports on errors, and examine possible workarounds: The line number of the exception is reported in a standard stack trace, but which variable or method on that line caused the actual exception?
— Takipi (@takipid) August 11, 2016
The typical error resolution workflow
While the issue we’re covering here isn’t exclusive to NullPointerExceptions, it makes a good simple example. After all, they’re the most common exception in Java production environments.
Let’s assume a NullPointerException happened and you’re tasked with investigating its root cause so you can fix it.
The starting point is the relevant log data and the exception’s corresponding stack trace:
Now, let’s clear the noise and strip down the 3rd party code to stay with the most relevant information:
We see there’s a NullPointerException on line number 64 in the GetUserBillingServlet class. When we follow through and examine the code, there are 2 scenarios that can happen. The snakes and ladders of debugging:
1. We’re in luck, there’s only one value that could’ve been null on that line and maybe we’ve also logged it in a few different spots in the code so we can narrow down on the problematic step. Something like:
The “user” object is definitely the source of trouble.
2. Murphy’s law. If something can go wrong, it will go wrong. Consider the following if statement:
Now we’re not sure if it’s the “user” or “account” who are null and we’re stuck.
As Mr. T once said, “Life’s tough, but I’m tougher”. Let’s look into some possible solutions that would help us advance the investigation.
Solution #1: Breaking down complex code lines
In the above example, the if statement could have been broken down to:
The stack trace would include the appropriate line number and let us move forward faster. This is also why splitting Java 8 aggregate operations on streams is a good practice.
In fact, some style guides insist on the same principle also for readability issues. Check out the post where we compared Java style guides from companies like Google, Twitter and Mozilla (and Pied Piper).
Solution #2: More null checks
This is probably the most obvious solution, keeping them nulls at check and making sure no rogue values pass to critical areas. Code filled with null checks is not pretty, but sometimes it’s a necessary evil.
In a previous post about JVM JIT optimization techniques we’ve elaborated on how the JVM makes use of the common trap mechanism to work around possibly redundant null checks that affect performance.
Solution #3: Higher verbosity logging
If there’s an exception, there’s usually a log message which contains additional hints. Whether it will contain useful information or not is a different story.
The next step could be to add information to the message or create new log messages that would shine some light on the path to the… explosion. Which creates the debugging paradox – hoping the error would happen again to make it stop from happening again.
For additional methods to debug production servers at scale, check out this post on the High Scalability blog (which is a great resource for anything related to high scale systems).
Solution #4: Developer tools to the rescue
At OverOps, we’re developing a tool that solves this issue among others. Whenever an exception, logged error or warning occurs, OverOps analyzes it and show the variable state at the moment of error with the code that caused it.
This way, no matter what’s the issue, and if it’s a NullPointerException or any other exception, the variable values that caused it are right there for the taking:
You can check it out right here, we’d be happy to hear what you think.
Exceptions aren’t going anywhere anytime soon and it’s better we have a good strategy in place to identify, prioritize and resolve them. Have any other tips for Java exception handling? Please post them in the comments section below.