Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Java NullPointerException: How To Write Diagnostic Code

DZone's Guide to

Java NullPointerException: How To Write Diagnostic Code

Some great tips on writing code that, when problems inevitably happen, is easy to troubleshoot.

· Java Zone ·
Free Resource

Automist automates your software deliver experience. It's how modern teams deliver modern software.

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?

Java / Scala developer? Takipi replaces logging in production JVMs and lets you see the variable state that caused each log error and exception.

Let’s roll.

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:

java.lang.NullPointerException: null
        at com.sparktale.bugtale.server.app.servlet.billing.GetUserBillingServlet.internalWork(GetUserBillingServlet.java:64) [GetUserBillingServlet.class:na]
        at com.sparktale.bugtale.server.app.servlet.billing.GetUserBillingServlet.internalWork(GetUserBillingServlet.java:27) [GetUserBillingServlet.class:na]
        at com.sparktale.bugtale.server.app.servlet.AppServicesProtoServlet.work(AppServicesProtoServlet.java:82) [AppServicesProtoServlet.class:na]
        at com.sparktale.bugtale.server.app.servlet.AppServicesProtoServlet.work(AppServicesProtoServlet.java:21) [AppServicesProtoServlet.class:na]
        at com.sparktale.bugtale.server.common.servlet.CommonServlet.handleRequest(CommonServlet.java:144) [CommonServlet.class:na]
        at com.sparktale.bugtale.server.common.servlet.CommonServlet.doPost(CommonServlet.java:64) [CommonServlet.class:na]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:647) [servlet-api.jar:na]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:728) [servlet-api.jar:na]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305) [catalina.jar:7.0.42]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.42]
        at org.apache.catalina.filters.ExpiresFilter.doFilter(ExpiresFilter.java:1179) [catalina.jar:7.0.42]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.42]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.42]
        at org.apache.catalina.filters.AddDefaultCharsetFilter.doFilter(AddDefaultCharsetFilter.java:88) [catalina.jar:7.0.42]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243) [catalina.jar:7.0.42]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) [catalina.jar:7.0.42]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222) [catalina.jar:7.0.42]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123) [catalina.jar:7.0.42]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) [catalina.jar:7.0.42]
        at com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve.invoke(RedisSessionHandlerValve.java:26) [tomcat-redis-session-manager-1.2.jar:na]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171) [catalina.jar:7.0.42]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99) [catalina.jar:7.0.42]
        at ch.qos.logback.access.tomcat.LogbackValve.invoke(LogbackValve.java:189) [logback-access-1.1.2.jar:na]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) [catalina.jar:7.0.42]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408) [catalina.jar:7.0.42]
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1023) [tomcat-coyote.jar:7.0.42]
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589) [tomcat-coyote.jar:7.0.42]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1686) [tomcat-coyote.jar:7.0.42]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_65]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_65]
        at java.lang.Thread.run(Thread.java:745) [na:1.7.0_65]


Now, let’s clear the noise and strip down the 3rd party code to stay with the most relevant information:

java.lang.NullPointerException: null
        at com.sparktale.bugtale.server.app.servlet.billing.GetUserBillingServlet.internalWork(GetUserBillingServlet.java:64) [GetUserBillingServlet.class:na]
        at com.sparktale.bugtale.server.app.servlet.billing.GetUserBillingServlet.internalWork(GetUserBillingServlet.java:27) [GetUserBillingServlet.class:na]
        at com.sparktale.bugtale.server.app.servlet.AppServicesProtoServlet.work(AppServicesProtoServlet.java:82) [AppServicesProtoServlet.class:na]
        at com.sparktale.bugtale.server.app.servlet.AppServicesProtoServlet.work(AppServicesProtoServlet.java:21) [AppServicesProtoServlet.class:na]
        at com.sparktale.bugtale.server.common.servlet.CommonServlet.handleRequest(CommonServlet.java:144) [CommonServlet.class:na]
        at com.sparktale.bugtale.server.common.servlet.CommonServlet.doPost(CommonServlet.java:64) [CommonServlet.class:na]


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. 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:

  2. if(user.isCustomer() != false) {
      …
    }


    The “user” object is definitely the source of trouble.
  3. Murphy’s law. If something can go wrong, it will go wrong. Consider the following if statement:

if(user.isCustomer() != false && account.equals(id)) {
  ...
}

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:

if ((user.isCustomer() != false) &&
    (account.equals(id))) {
  ...
}

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 Takipi, we’re developing a tool that solves this issue among others. Whenever an exception, logged error or warning occurs, Takipi 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 are right there for the taking:

LogLink

You can check it out right here, we’d be happy to hear what you think.

Final Thoughts

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.

Java / Scala developer? Takipi replaces logging in production JVMs and lets you see the variable state that caused each log error and exception.

Get the open source Atomist Software Delivery Machine and start automating your delivery right there on your own laptop, today!

Topics:
exception ,java ,stack ,error ,null ,null checks ,stack trace

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}