I have spent some time in the past couple of days helping a platform developer on my team sort out some classloader
issues related to a platform application. I have a suggestion for making it easier to identify and solve these problems
in the future.
He was attempting to integrate a servlet engine (Jetty) into the NetBeans Platform and received a
ClassNotFoundException at runtime:
at java.security.AccessController.doPrivileged(Native Method)
Caused: java.lang.NoClassDefFoundError: javax/servlet/http/HttpServlet
at java.lang.ClassLoader.defineClass1(Native Method)
As you can see, it is not an easy stack trace to follow. And while it clearly tells him what class cannot be found
(javax.servlet.http.HttpServlet), it does not identify the class or module which is attempting to load it. In this
case, the com.mycompany.myapp.mymodule.SomeClass (obviously not the real name) class is not the one which was trying to
load the HttpServlet class, but rather the one which was attempting to start Jetty (a library wrapper module), and Jetty
was trying to load HttpServlet. I've truncated the stacktrace slightly here, but nowhere in the original version is any
Jetty class mentioned.
The solution was to add a dependency from the Jetty module to the Servlet API module, but this was not immediately
obivous as nowhere in the stacktrace does it mention the Jetty module or the class in that module which tried to load
So I am suggesting an improvement to the module class loader which is that the ClassNotFoundException message be
enhanced to identify the actual caller, perhaps using code like this:
StackTraceElement trace = Thread.currentThread().getStackTrace();
StackTraceElement elem = trace;
String caller = elem.getClassName() + "#" + elem.getMethodName();
String message = "ClassNotFoundException trying to load " + name + ", possibly from " + caller;
Something like this would have saved a lot of time trying to debug the problem.
This is probably unfixable in NB. There are already various RFEs open for the JRE to produce more useful NCDFE's which
actually tell you what was going on when class loading fails. The NB module class loaders already give detailed messages
for CNFE, but the message you see is from URLClassLoader, not NB.
I think your suggested fix is off target. The JRE is already printing the full stack trace; Jetty code is really not in
the stack at all. It is the VM which is invoking the class loading which fails here, as part of resolving another class,
and the VM simply does not expose any information about why it is trying to load the visible class.
If you attach a minimal, self-contained, reproducible test case I can at least take a look and try to see if there is
any way NB can make more information visible to the developer.
BTW if your Jetty module was missing a dep on the Servlet API, you should have been able to see this without even
running the app: <verify-class-linkage>, run during the build of a module, prints warnings about classes in the module
which would not be resolvable if loaded at runtime, according to the module's declared dependencies.
I agree with the assertion about my suggested fix being off target... It was the first thing that came to mind.
I have just created a minimal, self-contained, reproducible test case which I will attach shortly. One key thing to
note is that one could probably only encounter this problem when library modules are being used, as they are compiled
outside of the NB build harness. Obviously the classpath used to originally compile them would have to include all
referenced classes/interfaces, but those same dependencies may not be present at runtime.
As you speculated, verify-class-linkage did warn about the missing class at build time. There are many such warnings
shown and since most can be safely ignored (e.g. optional dependencies in third-party libraries), so they tend to get
lost in the Ant output. But checking that is a good first step when debugging CNFE and so I will recommend this to the
developers and also add an entry into the NB Developer FAQ.
ZIP attached with the suite that demonstrates the problem (although not quite as well as the original Jetty/Servlet API
Module C defines an interface (SomeInterface) in a JAR file and Module B contains a JAR file which has an
implementation of that interface (SomeImplementation), plus a class InvokerThingie which provides a static method that
calls into SomeImplementation. Module A defines an action which invokes InvokerThingie.
Steps to reproduce:
1. Unpack and run the suite
2. Click File menu
3. Select "Invoke Greeter..." item
This throws a "java.lang.ClassNotFoundException: com.tomwheeler.example.c.publicapi.SomeInterface", but it is not
immediately obvious what needs it.
Upon further reflection, I think it would have been less obivous still what the problem was in the stacktrace had the
InvokerThingie class been in a different module than the SomeImplementation class.
I have attached a classloading_improvement_example_suite_v2.zip suite which does a better job of illustrating the
problem, by adding an additional module B2 which contains the InvokerThingie class originally in Module B. Module B
now only contains the SomeImplementation class in a JAR file.
When the menu item is invoked, there is no mention of module B whatsoever. Therefore, aside from the verify-class-
linkage warning from the build, there is no indication that the solution is to add a dependency from Module B onto
Oddly, when invoked a second time during the same session, the stacktrace is completely different (it's a
NoClassDefFoundError rather than a CNFE). It references the implementation class as unavailable rather than the
interface. This is similar to the behavior I noticed while trying to debug the Jetty/Servlet API problem.
This is similar
The reason the second invocation gives NCDFE on the impl class is that the impl class failed to resolve correctly the
first time, and so the class loader marks it as known to be unloadable (or something like this).
I found a place where I can insert the information about the trigger class. It works on your example, though I cannot
promise it will work on other cases. Please try to verify in a realistic situation.
Nice work, Jesse. I built a local copy of the platform with this change and was able to verify not only my
demonstration suite but also on the original application that prompted me to file the issue. In both cases, the error
message was much more clear and would help a developer realize which dependencies need to be added.
Thanks again and I am going to go vote for the JDK issue you filed.
Well, I was going to vote for the JDK bug but could not find it. There is no JDK bug #6747450 at bugs.sun.com and the
most recent one I found doing a search for 'jglick' was an unrelated issue from September 3, 2008.
bugs.sun.com receives reports from the internal tracker in batch mode. It will likely appear within a few days. Yes I
hate it too and hope that the OpenJDK project switches to a publicly visible (at least) tracking system soon.
Comment 14Quality Engineering
2008-09-12 06:07:36 UTC