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. :(
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."
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.
> 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.
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).
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)
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.
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.
> 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.
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.
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.
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.
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();
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.
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.
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.
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.
Maybe or optional types don't really help because your "get value" functions also throw exceptions, or have undefined behavior, if the value doesn't exist.
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.
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"
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.
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.
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":
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.
Of course, an Optional reference can still be null because it's still Java. There are also other implementations of Maybe-like types, and implementations of Either-like types, etc in the Java ecosystem.
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!
> 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.
> 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.
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.
>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].
[+] [-] cpeterso|7 years ago|reply
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
This is why we can't have nice things.
[+] [-] richdougherty|7 years ago|reply
[+] [-] ubertaco|7 years ago|reply
[+] [-] keymone|7 years ago|reply
I am Jack's complete lack of surprise.
[+] [-] snvzz|7 years ago|reply
And by all means, break them. They're undefined behaviour.
[+] [-] eneveu|7 years ago|reply
[+] [-] the_duke|7 years ago|reply
"Every change breaks someones workflow..."
[+] [-] unknown|7 years ago|reply
[deleted]
[+] [-] runarberg|7 years ago|reply
Edit: Formatting
[+] [-] bzbarsky|7 years ago|reply
[+] [-] zamadatix|7 years ago|reply
[+] [-] tarabanga|7 years ago|reply
[+] [-] Insanity|7 years ago|reply
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
"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
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
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
https://docs.microsoft.com/en-us/dotnet/csharp/language-refe...
[+] [-] jason0597|7 years ago|reply
[+] [-] Someone1234|7 years ago|reply
https://msdn.microsoft.com/en-us/magazine/mt829270.aspx?f=25...
[+] [-] didymospl|7 years ago|reply
See also Brian Goetz opinion on that subject: https://youtu.be/FdkPHShh628?t=50m17s
[+] [-] joemccall86|7 years ago|reply
[+] [-] EmpirePhoenix|7 years ago|reply
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
[+] [-] xxs|7 years ago|reply
[+] [-] js8|7 years ago|reply
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
[+] [-] unknown|7 years ago|reply
[deleted]
[+] [-] xxs|7 years ago|reply
[+] [-] _Codemonkeyism|7 years ago|reply
I've left this year the JVM for TS, but glad for everyone who uses the JVM.
[+] [-] hnthroaway1926|7 years ago|reply
[+] [-] phinnaeus|7 years ago|reply
[+] [-] akerro|7 years ago|reply
[+] [-] yxhuvud|7 years ago|reply
[+] [-] tantalor|7 years ago|reply
Examples:
https://en.cppreference.com/w/cpp/utility/optional/value
https://docs.oracle.com/javase/8/docs/api/java/util/Optional...
[+] [-] gldev3|7 years ago|reply
[+] [-] edoo|7 years ago|reply
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
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
[+] [-] kbenson|7 years ago|reply
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
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:
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 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": 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: Conversely, if a function says it returns an 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.
[+] [-] oftenwrong|7 years ago|reply
https://docs.oracle.com/en/java/javase/12/docs/api/java.base...
Of course, an Optional reference can still be null because it's still Java. There are also other implementations of Maybe-like types, and implementations of Either-like types, etc in the Java ecosystem.
[+] [-] eckza|7 years ago|reply
[+] [-] unknown|7 years ago|reply
[deleted]
[+] [-] repolfx|7 years ago|reply
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
[+] [-] Myrmornis|7 years ago|reply
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
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
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) {
}In this case, a nullpointerexception would include the line number (using default compiler options) clearly indicating which is null.
[+] [-] nickodell|7 years ago|reply
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...