Bug 145503

Summary: Improve error message for ClassNotFoundException for modules
Product: platform Reporter: tomwheeler <tomwheeler>
Component: Module SystemAssignee: Jesse Glick <jglick>
Status: VERIFIED FIXED QA Contact: issues <issues.netbeans.org>
Priority: P3    
Version: 6.x   
Target Milestone: 6.x   
Hardware: All   
OS: All   
Whiteboard: jdk_bug_6747450
Issue Type: ENHANCEMENT Exception Report:
Attachments: Minimal example NB 6.5 suite to reproduce the problem described
Suite which does a better job of illustrating the problem

Description tomwheeler 2008-08-28 23:37:06 UTC
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:

 java.lang.ClassNotFoundException: javax.servlet.http.HttpServlet
 	at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
 	at java.security.AccessController.doPrivileged(Native Method)
 	at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
 	at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
 	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:276)
 	at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
 	at org.netbeans.ProxyClassLoader.loadClass(ProxyClassLoader.java:244)
 	at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
 	at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
 Caused: java.lang.NoClassDefFoundError: javax/servlet/http/HttpServlet
 	at java.lang.ClassLoader.defineClass1(Native Method)
 	at java.lang.ClassLoader.defineClass(ClassLoader.java:620)
 	at org.netbeans.JarClassLoader.doLoadClass(JarClassLoader.java:243)
 	at org.netbeans.ProxyClassLoader.selfLoadClass(ProxyClassLoader.java:254)
 	at org.netbeans.ProxyClassLoader.loadClass(ProxyClassLoader.java:228)
 	at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
 	at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
 	at com.mycompany.myapp.mymodule.SomeClass.init(SomeClass.java:59)

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
HttpServlet.  

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[2];
    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.
Comment 1 Jesse Glick 2008-08-29 00:30:44 UTC
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.
Comment 2 tomwheeler 2008-08-29 01:17:42 UTC
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.
Comment 3 tomwheeler 2008-08-29 01:19:12 UTC
Created attachment 68591 [details]
Minimal example NB 6.5 suite to reproduce the problem described
Comment 4 tomwheeler 2008-08-29 01:33:33 UTC
ZIP attached with the suite that demonstrates the problem (although not quite as well as the original Jetty/Servlet API 
app).

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.
Comment 5 tomwheeler 2008-08-29 01:42:24 UTC
Created attachment 68592 [details]
Suite which does a better job of illustrating the problem
Comment 6 tomwheeler 2008-08-29 01:49:47 UTC
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 
Module C.

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 
Comment 7 tomwheeler 2008-08-29 17:36:46 UTC
FYI I added a Wiki page:

   http://wiki.netbeans.org/DevFaqTroubleshootClassNotFound

which describes the problem and how to interpret <verify-class-linkage> to aid in solving it.
Comment 8 Jesse Glick 2008-09-11 17:01:16 UTC
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).
Comment 9 Jesse Glick 2008-09-11 17:22:35 UTC
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.

core-main #5ee06cf4edfe
Comment 10 Jesse Glick 2008-09-11 18:03:06 UTC
I filed a request for the JVM to do the right thing to begin with.
Comment 11 tomwheeler 2008-09-11 21:32:30 UTC
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.
Comment 12 tomwheeler 2008-09-11 21:49:21 UTC
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.
Comment 13 Jesse Glick 2008-09-11 22:19:17 UTC
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 14 Quality Engineering 2008-09-12 06:07:36 UTC
Integrated into 'main-golden', will be available in build *200809120201* on http://bits.netbeans.org/dev/nightly/ (upload may still be in progress)
Changeset: http://hg.netbeans.org/main/rev/5ee06cf4edfe
User: Jesse Glick <jglick@netbeans.org>
Log: #145503: improve diagnostics for NCDFE.
By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2014, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo