Jun 13 2007
NullPointerExceptions Are Not Helpful
I knew it wouldn’t be long before I encountered a great example of how Hibernate does not qualify as making ORM easy (despite what Gavin King might say).
Here’s a good one: Hibernate generated a painfully slow query to load a collection, which becomes quite fast when written slightly differently. So I’m trying to write a mapping file with a custom SQL query by request of the local DBA, but when I run the mapping file unit tests, I get the following exception:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory' defined in class path resource [com/clientname/testContext.xml]: Invocation of init method failed; nested exception is java.lang.NullPointerException
Caused by: java.lang.NullPointerException
at org.hibernate.loader.custom.sql.SQLQueryParser.resolveProperties(SQLQueryParser.java:182)
at org.hibernate.loader.custom.sql.SQLQueryParser.resolveCollectionProperties(SQLQueryParser.java:135)
at org.hibernate.loader.custom.sql.SQLQueryParser.substituteBrackets(SQLQueryParser.java:98)
[...]
That’s completely worthless for any kind of debugging purpose. I’m following the example as precisely laid out in Java Persistence with Hibernate (co-authored by Gavin King and Christian Bauer), and I’m looking through the meager Hibernate documentation, and I’ve got nothing.
Now, I’ve got two options. I can either try to plow through the Hibernate source code to figure out what it is that is being set to null and how to stop it from being so, or I can try Monte Carlo debugging — just change random crap in hopes of getting a more meaningful error message.
Pain free my keister.
Popularity: 8% [?]
Null pointer exceptions are still better than what C/C++ gives you, which is segfaults. The problem is that they didn’t really solve the problem- and this is the biggest failure of Java, a failure of imagination to see the real problem and how to solve it. Generally, the error in the code isn’t where you threw the null pointer exception, the error in the code is where the value got set to null (or, more likely, didn’t get set at all). The real solution is that you need to be able to specify “nullability” as a type constraint- to be able to say that this variable or parameter can never be null.
Of course, once you do that, a whole host of problems start arising. For example, what initial value do non-nullable values get? Obviously, they have to be given a value immediately upon allocation. How do you specify that this nullable variable is now known to be not null and can therefor be safely assign to this non-null variable? And so on. Pretty soon you start talking about a type calculus (even if you aren’t using that term), and the ML/Ocaml/Haskell programmers start wandering over.
I had a cursory look at the code, and couldn’t see any obvious causes… In any case I would say it’s certainly a bug that you’re seeing an NPE rather than a more appropriate exception.
Looks like you found a bug in Hibernate, since I doubt the Hibernate devs are intentionally throwing a NPE (or allowing one to occur). Running across bugs in libraries can be frustrating, but it’s not rare enough to be particularly noteworthy, IMHO. (Or maybe I’ve spent so much time with Maven 2 that I’m becoming jaded and starting to expect bugs. ;) ).
Why not run your code through a debugger, place a breakpoint just before the NPE, and follow the stack trace up until you find what is null? Doesn’t seem like that’s too difficult to me. I’m sure that would take less than half the time it took you to write this blog post. Just sayin’..
Okay, fair enough — in my frustration, I didn’t really address why delving into the bowels of Hibernate is not an option I’m okay with.
The fundamental problem is that this is commonplace. If this was a unique situation or an isolated occurrence, I’d be fine with it, and I’d even be willing to put some effort into improving the API (I’m doing that right now with Ruport).
However, it’s not an isolated case. It is, in fact, the standard case whenever you try to do something non-trivial in Hibernate: you are going to get an unhelpful exception which is going to be solved only by digging into all the data and all the mapping files and all the limited documentation and then finally taking a pot-shot at something that seems kind of odd. Another example: the other day I got a LazyInstatiationexception: “no session attached to collection” (or something like that). The problem was that a the object which had the collection that could not be lazily instatiated appeared in a join table twice, so two versions of the object were getting loaded into cache, and only the first got the session. The explosion wasn’t anywhere near that point, though: it was on an attempt to initialize a collection that contained in the redundant object! That took me a long number of hours to finally debug, and I started there by digging deep into the bowels of Hibernate.
Despite this hostility towards its users and a kind of global frustration, Hibernate has become the industry standard. So I can pretty much guaranty that I will be dealing with it as long as I’m coding Java.
Add on top of that frustration Gavin King declaring ORM to be a solved problem, and it just made me cranky.
@CuriousGeorge
That’s not the hard part. It’s more-or-less easy to figure out what is null. What’s hard is figuring out why it is null, and how to get Hibernate to make it not null. Like Brian said above: the real problem isn’t at the point where something is null, the problem is at the point where something was set to null (or not set at all).
In my code, I spend a lot of time writing methods like this:
This is precondition/postcondition testing, so that I don’t end up with a random unhelpful
NullPointerExceptionbeing thrown — if a user of my code screwed up using it, they’ll know right away what the problem is and can fix it. This is being friendly to your users.If I weren’t to do that, I may pass
barMeinto another couple dozen of methods (or, worse, set it as properties in objects I return), which means that when it beingnullfinally explodes, the user is getting a stack trace far from where the original problem actually was. This is allowing your users to plant bombs in their own code, which is not friendly.And when a library isn’t friendly to me, it’s exceedingly frustrating. When the library is exceedingly complicated and exceedingly frustrating, and I’m forced to try to dig into that to figure it out, it’s flat-out rude.
Whatever happened to good old assertions that get compiled out in release mode?
@Chui Tey
Or, better yet, that get left in (or at *least* logged was warnings) in release code? See Pragmatic Programmer and Release It! for a good discussions on this.
These kinds of things never made it into Java’s culture, because its assertion functionality was very late to the party and pretty awkward to use.
The bug is apparently in parsing the query. The method
ParserContext#getEntityPersisterByAliasis returningnullwhen asked for the aliasp(which is defined as a load-collection alias, as well as a table alias in my query). That persister is then exploding on its next use. So the surprise happens at line 174, but it explodes at either 182 or 191, depending on what the query looks like.Blargh.
ParserContextis an undocumented interface, whose implementation is being passed in.This is where abstraction ceases to be fun.
I think that’s Las Vegas debugging, technically ;)
[...] This code is pretty common: T out = null; if(someTest(in)) { out = value1; } else if(someOtherTest(in)) { out = value2; } else { out = value3; } doSomethingWith(out) If you change T out = null; to just final T out;, the compiler will check that all code paths leading to “doSomethingWith(out)” will lead to “out” being assigned. And you can wipe out one more null in your code, which means one fewer opportunity for a NullPointerException to sneak out of your library and piss me off. [...]
[...] BEEN FiXED WITH STATIC TYPING. Even more if you have a type system which can check nulls for you. Null pointers/”nil when you didn’t expect it!” errors are totally solvable problems. The fact [...]
I like JetBrains’ solution to the problem:
http://www.jetbrains.com/idea/documentation/howto.html
There’s no reason to ever get an NPE if you’re using that.
In this case, the NPE was coming out of Hibernate. My IDE will not fix that.
Also note that @nullable and @notnull are type annotations, in the sense of Ocaml or Haskell types (Ocaml calls nullable values “options”, Haskell “maybes”- same idea).
Which means that not only should this capability (among others) be expressed in the type system, the compiler should be able to deduce whether or not a particular value is nullable or not automatically- the annotation should be optional. Again, Haskell, Ocaml, et. al. do precisely this this.
[...] right. If you switch to Ocaml, the compiler will guaranty that you never see a $!# NullPointerException (or equivalent) again. Ever. [...]