This is worth a quick post as I couldn’t find a solution on Google and it took me an hour or two of fiddling.
I have a Maven project, call it frontend-war, which contains the main service code for FuncNet. A unit test kept failing in Eclipse with NoSuchMethodError, one of my least favourite screw-ups to disentangle.
In this case it was particularly frustrating, as the method (on a class in one of the main project’s dependent jars, called service-utils) definitely existed, was public, and had the right signature. Also, even more weirdly, when I ran the tests in Maven from the command line, they passed.
Cue all the usual Eclipse cargo-cult dead-chicken-waving — cleaning everything, closing and reopening projects, etc. etc. No joy.
Then it hit me… (solution below the jump)
One of the other jars included by frontend-war, called sessionDB, also included service-utils — and was using an out-of-date version. This version didn’t include the method I was calling (at least not with that signature). The dependency hierarchy looked something like:
frontend-war-CURRENT
service-utils-1.2.9
…
sessionDB-1.3.3
service-utils-1.2.4
…
So I rebuilt sessionDB against the latest version of service-utils, then rebuilt frontend-war against that, and it worked.
So, my fault for getting myself into jar hell, although even if you don’t bring it on yourself, it’s hard to avoid it if you use a lot of open-source components, and Maven — which I still argue is a timesaver overall — tends to exacerbate it. Take a look at the Dependency Graph view in Eclipse’s POM Editor sometime — if you see any red arrows, that means two or more different versions of a dependency are being requested by different modules within your project.
So why did it work on the command line and not in Eclipse? To be honest, I don’t know in detail. But in cases like this, the classloader will (AFAIK) just pull in whichever version is requested first, so Eclipse’s classpath management must have happened to process the dependencies in a different order from Maven’s — meaning it worked by pure luck.
Note: My solution — updating sessionDB to use the latest service-utils — wasn’t a problem, because all three of the modules in the unholy ménage à trois belonged to me. But what do you do if two third-party libraries request different versions of the same jar?
A famous notorious example of this tends to happen with Hibernate, which depends on a really out-of-date version of ASM — or at least it used to, I haven’t checked recently. Lots of open-source projects use ASM but more recent versions clash nastily with the old one Hibernate requires.
The solution in this case is to use Maven’s exclusions clause to specifically exclude ASM from Hibernate’s transitive dependencies. Thankfully it can just use the more recent versions without problems.
But what would you do if one of the libraries you were using depended on a method that was no longer available in a more recent jar? Then you’re really in jar hell. Answers on a postcard…
{ 3 } Comments
We had this recently when trying to use a local copy of JUnit (one that supported newer Hamcrest matchers) in Plugin Test configs. Standard test configs worked fine in Eclipse but plugin test configs errored even though the methods were obviously correct.
To try to explain the detail (I debugged right in to it – one of the many uses of the OpenJDK source!) the JUnit process loops over all of the methods on a class and puts them in a map against their appropriate annotation. It then fetches the set of methods for the “test method” annotation and runs them. If it can’t find any then it throws the error you saw. The problem comes if the annotation it uses to fetch methods is a different version in a different class loader to the one that the method itself was annotated with. In that case you end up with everything being cached against the wrong annotation – it is there, but JUnit doesn’t see it when it retrieves it.
Our solution was to move to an Eclipse 3.6 milestone and use a downloaded JAR of the Hamcrest matchers (because, oddly enough, it only picks up the newer one of those), but that won’t help with most Maven builds.
IBBoard — just to clarify, I don’t think my particular situation was JUnit-specific. This was just where I happened to run into the particular problem.
It was during a unit test, but the method call that errored out was actually in the class under test.
Thanks for comment though, I suspect there’s lots of nasty classloading problems that can cause the same error. Yours sounds much nastier…
You’re right, it probably isn’t JUnit specific and almost certainly is just a general class loading issue. In our case it was just that JUnit an Plug-in Tests triggered the problem in an obvious way because the Plug-in Test loader loaded the Eclipse bundled JUnit, but our plugin then user our own “testing utilities” plugin, which had its own (newer) copy of JUnit, which lead to the interface annotation being loaded from two different places. Ideally there should have been an error from it somewhere about duplicate classes, but instead it happily carried on and the difference only reared its head when it came to trying to find test methods.
And I always thought that Java was supposed to be better than C++/C# because you didn’t get DLL Hell! We’ve just replaced one hell for another.
Post a Comment