top | item 19492660

Add detailed message to NullPointerException describing what is null

345 points| dhotson | 7 years ago |openjdk.java.net

212 comments

order
[+] cpeterso|7 years ago|reply
When Firefox tried to make the "X is undefined" JavaScript exception message user-friendlier, it broke flipkart.com. The website's JavaScript explicitly depended on the exact wording of exception messages. Simply loading the flipkart.com home page caused "X is undefined" exceptions, which it tried to parse with regular expressions. The new exception message had to be reverted. :(

https://www.fxsitecompat.com/en-CA/docs/2018/improved-javasc...

This is an unfortunate example of Hyrum's Law: "With a sufficient number of users of an API, all observable behaviors of your system will be depended on by somebody."

http://www.hyrumslaw.com/

[+] rwz|7 years ago|reply
Software relying on undefined behavior like that deserves to be broken and made fun of. I fully support breaking it and the idea that they had to revert the error message change horrifies me.

This is why we can't have nice things.

[+] ubertaco|7 years ago|reply
Why in the world was Flipkart regexing for an "X is undefined" message? That seems as brittle as possible.
[+] keymone|7 years ago|reply
> When Firefox tried to make the "X is undefined" JavaScript exception message user-friendlier, it broke flipkart.com. The new exception message had to be reverted.

I am Jack's complete lack of surprise.

[+] snvzz|7 years ago|reply
>all observable behaviors of your system will be depended on by somebody.

And by all means, break them. They're undefined behaviour.

[+] eneveu|7 years ago|reply
Their code explicitly depended on the wording of exception messages because they were using a dynamic language. Java is a typed language, where we can match on exception types:

  if (ex instanceof NullPointerException)
  catch (NullPointerException ex) {
It is therefore rare for Java programmers to match on exception messages (it would be a mistake).
[+] runarberg|7 years ago|reply
In JavaScript I often find my self running the code in both firefox and chrome to get the full picture of what is undefined: (firefox)

    window.foo.bar
    //=> TypeError: window.foo is undefined
and what I'm trying to get: (chrome)

    window.foo.bar
    //=> Uncaught TypeError: Cannot read property 'bar' of undefined
Personally I find the firefox error message more useful, but often I get a better understanding of whats wrong when I run it in chrome. I don’t know why they are mentioning undefined at all and don’t say something like:

   TypeError: Cannot read property 'bar' of 'window.foo' (undefined)
---

Edit: Formatting

[+] bzbarsky|7 years ago|reply
The worst part is that Firefox tried to change the error message it throws to include the "bar" piece of information, and that had to be reverted because it broke sites that were parsing the exception message with regexps. :( See https://bugzilla.mozilla.org/show_bug.cgi?id=1498257 for the gory details, though it was not the only site affected: see also https://bugzilla.mozilla.org/show_bug.cgi?id=1490772 (fixed by site author) and https://bugzilla.mozilla.org/show_bug.cgi?id=1512401 (fixed by the backout).
[+] zamadatix|7 years ago|reply
On Chrome if you click the hyperlink on the far right side of the error it brings you to the line that caused the error. Not as easy as if it were displayed as part of the error but easier than switching between browsers.
[+] tarabanga|7 years ago|reply

    TypeError: Cannot read property 'bar' of undefined 'window.foo'
[+] Insanity|7 years ago|reply
This sounds like a good addition.

It happens quite often that we get a bug report with "We got a NPE". Our users often attach a screenshot, just showing the standard "NPE error" message, which does not really help.

[+] evancox100|7 years ago|reply
> Computation overhead > NullPointerExceptions are thrown frequently.

"Frequently" is obviously a relative term, but are NullPointerExceptions really common enough to be a performance concern? It's good that they are taking performance overhead into consideration of course, I'm just surprised it's even an issue.

[+] bhauer|7 years ago|reply
Agreed. I feel the key word in dealing with exceptions is "exception," meaning something uncommon, which is a foreign concept in some codebases, where exceptions are used as routine flow-control.

Throwing an exception in Java is a non-trivial expense since the constructor for Throwable populates a stack-trace structure for the current thread. If you're using exceptions as a routine flow-control mechanism, you're incurring that cost too frequently.

Like you said, sure, tune the performance of this new feature because it's part of the standard library and should be reasonably well-tuned. But if people are frequently throwing NPEs, they should consider alternatives such as simple null checks.

[+] paulmd|7 years ago|reply
A lot of those NullExceptions (and a lot of boilerplate code) could be linted away if Java gave compiler-level semantics to @NotNull annotations.

Letting null values propagate through your program is frequently a code smell, you want to constrain it at the boundaries of your program logic if possible. Unfortunately, Java does not have a semantic way to do this, just documentation and discipline.

[+] theandrewbailey|7 years ago|reply
This would be good, but I'd prefer that Java have something like the null operators that C# has.

https://docs.microsoft.com/en-us/dotnet/csharp/language-refe...

[+] didymospl|7 years ago|reply
Adding Elvis and safe navigation operators to Java was discussed before Java 7, over 10 years ago. Sadly they never made it even to the official proposal lists and with Optional introduced in Java 8 I don't think we'll get them any time soon.

See also Brian Goetz opinion on that subject: https://youtu.be/FdkPHShh628?t=50m17s

[+] joemccall86|7 years ago|reply
This would be immensely beneficial, and long overdue in my opinion.
[+] EmpirePhoenix|7 years ago|reply
Funny thing, some JVM's already do exactly this for many years:

Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: while trying to invoke the method de.hybris.platform.catalog.model.CatalogModel.getId() of a null object returned from de.hybris.platform.catalog.model.CatalogVersionModel.getCatalog();

[+] copperx|7 years ago|reply
It would be useful to know what JVM is that.
[+] xxs|7 years ago|reply
this is a json serializer, =NOTHING= to do with compiled dererences.
[+] js8|7 years ago|reply
I am not really that familiar with Java, but I don't understand how to get a JVM heap dump if there is an exception that brings down the JVM or even a thread. Is it even possible?

I work on mainframe (z/OS), and it is completely normal there that when an application fails with exception (they're called ABENDs - from ABnormal ENDing), you can get a dump of memory of the application. From that, you can see all the values of the offending variables and all the relevant system areas and so on.

[+] jayd16|7 years ago|reply
Can we add column numbers to the stack traces? Seems like that would solve this in the general case.
[+] xxs|7 years ago|reply
no, not really. Java Class format doesn't support columns, just lines.
[+] _Codemonkeyism|7 years ago|reply
After 20 years, first in Java and then in Scala I've been tortured by this error message. Some years ago they fixed ClassCastException which for a long time also didn't tell you what was wrong. There are some others if I remember correctly with bad error messages, like NumberFormatException. In general error messages on the JVM are very bad, wish they would take a look at Elm error messages.

I've left this year the JVM for TS, but glad for everyone who uses the JVM.

[+] hnthroaway1926|7 years ago|reply
The default compiler options for java provide a lot of information on null pointer exceptions. I'm sort of blown away that so many on this thread are confounded by NPEs, in my view they are dead simple to track down if you have access to the source.
[+] phinnaeus|7 years ago|reply
If nothing else, it is really interesting to see exactly why this is hard.
[+] akerro|7 years ago|reply
Historic reasons, Java used to be bloated so things like these were not included, not computers are faster, have more memory and object metadata can be expanded.
[+] gldev3|7 years ago|reply
I don't know why i find this so funny but it's definitely something that would be greatly appreciated!
[+] edoo|7 years ago|reply
It is almost 2020 so it is reasonable to know what the actual error is.

This reminds me of old C/C++ parsers that couldn't tell you what line was expecting a bracket or semicolon and you got to manually find the parse error.

[+] opportune|7 years ago|reply
This is awesome! I ran into this exact issue yesterday evening trying to debug a NPE on foo.bar().baz().hello()

IMO overhead should not even be a consideration. It doesn't make sense to sacrifice detailed reporting just so someone can repeatedly handle NPEs as part of their application's logic. I can't imagine a situation in which I would look at code that throws enough NPEs for overhead to be a concern and say "yes this is well designed code"

[+] kazinator|7 years ago|reply
This is too much of a hack; I predict it will be unreliable, at times sending programmers on wild goose chases more time-wasting than an uninformed investigation.
[+] kbenson|7 years ago|reply
Because of some track record of Java, or because you just have a feeling about it?

I don't program in Java at all, and frankly, I was flabbergasted to see a change as obvious and useful as this wasn't already in there a decade ago. Or at least 3-4 years ago where a lot more languages started advertising better compilation error messages.

[+] jolmg|7 years ago|reply
I wonder if it's possible to add something similar to the protection you get in Haskell to other languages via some static analyzer or something.

In Haskell, it's pretty much impossible to have unexpected nulls because the ability to return nulls is something that is expressed in the type and needs to be handled before working with the possible type to be able to pass the type-check of the compiler.

For example, if I had a function:

  findFistOdd :: [Int] -> Maybe Int
That returned the first odd number in a list of numbers, the Maybe wraps the return value so the result is either `Nothing` or `Just someNumber`. I can't work with the result directly like

  1 + findFirstOdd [2,3]
It would fail the type check, because I need to tell the compiler what the program should do if findFirstOdd couldn't find an odd number. (1 +) is certainly not expecting a NULL as its argument type is Int. I can tell it, "Just die if that happens":

  1 + (fromMaybe (error "couln't find odd number") $ findFirstOdd [2,3])
or I can tell it to work on the inside value if it's there, returning Just (1 + someOddNumber) if it's there or Nothing if it's not:

  (1 +) <$> findFirstOdd [2,3]
Conversely, if a function says it returns an Int:

  addOne :: Int -> Int
That means it returns an Int, guaranteed. That's never going to be some null value. It's not something you'd ever have to consider.

Kind of wish all languages were like this. Nulls are probably the cause of most unexpected exceptions and it could be something that could always be caught without even running the program.

[+] eckza|7 years ago|reply
You’re looking for a nice little functional frontend web language called Elm.
[+] repolfx|7 years ago|reply
That's what Kotlin does. The only place NPEs can creep in with Kotlin code is:

a) Interop with Java APIs (Kotlin doesn't force you to check every return type from a non-Kotlin call)

b) If you use !! to cast away nullness. Good Kotlin style can almost always avoid such cases.

[+] joshmarlow|7 years ago|reply
I've long loved Option/Maybe types for this reason, but I've recently started to really lean into Result/Either types. When you can return the answer or an error, you can chain the errors together as the error goes up your stack and you have a nice trail leading from the calling code to the violated expectation - sometimes several libraries/abstraction boundaries away!
[+] Myrmornis|7 years ago|reply
> Given the bytecode, it is not obvious which previous instruction pushed the null value. To find out about this, a simple data flow analysis is run on the bytecodes.

This is a petty nit on what sounds like some great work but please do not use the word "simple" in technical contexts like this. Having you assert that it is simple does not make it easier for anyone to understand, and it is really quite irritating. I don't even know what "data flow analysis" is and I've been involved with computer programming and data analysis for 15 years.

[+] cesarb|7 years ago|reply
> I don't even know what "data flow analysis" is and I've been involved with computer programming and data analysis for 15 years.

You probably haven't been involved with compiler development in these 15 years; data flow analysis is one of the techniques used by optimizing compilers to reason about the code. Given the context (not only there is a compiler converting Java source code to Java bytecode, but also the Java Virtual Machine acts as a "just-in-time" compiler from the Java bytecode to machine code), everyone involved most probably already know what "data flow analysis" is. And by "simple" the author most probably means that a basic data flow analysis, with no extra bells and whistles, would be enough for the described purpose.

[+] hnthroaway1926|7 years ago|reply
Interesting, so they want to go beyond the line number of the exception and include information on what on the line is null.

It's sort of funny that their example doesn't follow the usual Java style guidelines of encapsulating member variables in the parent and using set() and get() methods for access.

a.to_b.to_c.to_d.num = 99;

This could be rewritten as a method in 'A' as...

protected void setDNum(int num) {

  C c = b.getC();

  D d = c.getD(); 

  d.setNum(99); 
}

In this case, a nullpointerexception would include the line number (using default compiler options) clearly indicating which is null.

[+] nickodell|7 years ago|reply
>As computing the NullPointerException message proposed here is a considerable overhead, this would slow down throwing NullPointerExceptions.

You could lower the overhead by not producing these messages if the exception is thrown frequently. It would be similar to the existing OmitStackTraceInFastThrow mechanism [0].

[0]: https://www.oracle.com/technetwork/java/javase/relnotes-1391...