Thursday, March 01, 2007

Java 6 == NoClassDefFoundError

I thought I might as well share this.

Problem

I'm working on an open source project and recently I tried to run the executable .jar file on my windows. It wouldn't run, which was surprising since I had done some basic testing on it and I had certainly been able to run it before.

That version still had no logging support, so I went to the console to try and run it, and sure enough it died with a:
java.lang.NoClassDefFoundError: com.sun.jdi.StackFrame

Background


That com.sun.jdi package quickly gave a pretty good idea about what was going on. Time for some background information.

First of all, the project had no external dependencies. I wasn't using anything that required extra jars to be packaged with the binary. This wasn't really as much a conscious decision as it was just something that happened.

When I added support for debugging another VM to my project, I used the com.sun.jdi.* classes from tools.jar that comes in a standard JDK. I had the bright idea to still not introduce a forced dependency, making this functionality optional: If the classes were found in the classpath the functionality would be there, if not, the rest of the application would still be usable.

I did this in a somewhat lazy and half-hearted, experimental manner, just testing around until it worked the way I wanted.

Reason for problems

So when I got that NoClassDefFoundError I knew my fragile construction had broken down. I initially assumed it had something to do with the changes to the Java 6 class verification, but instead it turned out that it was about changes to Swing classes: I had a JPanel subclass which had a method which had that JDI class in it's signature, and in Java 6 a call to the JPanel's constructor does something like:

this.getClass().getDeclaredMethods();

And that bit of reflection causes the classloader to try and verify all the classes referenced in the signatures of the methods (with any version of Java).

Fix

Having learned my lesson, I wrapped all the references to the JDI classes in any method signatures in my own local interfaces and now the project once again works fine with Java 6 as well.

Is this your problem?


I don't think that a lot of sensible people would try something like my original unplanned bubblegum attempt, but I do see some other possible scenarios where the same problem might arise. They all involve an existing system with some problems which are triggered by a change to Java 6.

For example, you might have a reusable component A which is compiled separately and has some dependencies to an external library B. The component A is used by two systems C and D. System C uses functionality which requires the library B and System D uses functionality that doesn't require the library B. For some reason library B hasn't been added to the classpath of the System D and it's never caused a problem before, but there's a reference to one of the classes in library B in some signatures of a user interface class and thus moving to Java 6 triggers that problem. Easy enough to fix adding the library B to the classpath, but a very annoying problem in the case of a maintenance system whose inner workings nobody is very familiar with.