Ribo
Ribo

Reputation: 3437

Why do I get a NoClassDefFoundError trying to use Google Drive in GAE in Java in Eclipse

I'm trying to use the Java Google Drive API in a servlet in Google App Engine (GAE) and get a NoClassDefFoundError when the servlet is first loaded in the (localhost) server in Eclipse.

As a test, I tried to create an instance of GoogleClientSecrets class in my servlet. Eclipse compiler doesn't complain, I have C:\app\gDrive\libs**google-api-client-1.16.0-rc.jar** in the Java Build Path, google-api-client-1.16.0-rc.jar contains the package com.google.api.client.googleapis.auth.oauth2 which contains GoogleClientSecrets.

The (localhost) server in the Google plugin in Eclipse loads as usual. When I the servlet class is first loaded (when the first request to it is made), I get the NoClassDefFoundError. If I comment out the reference to GoogleClientSecrets, the servlet works fine.

google-api-client-1.16.0-rc.jar is in the Eclipse -> Java Build Path -> Libraries list as an external jar file. It is 'check' marked in the Order and Export tab (which I think means that the jar file should be packaged in the war file -- an should be available to the server environment.

Is there some restriction on the use of these classes in GAE at run time? Is there something different about the classpath of the class loader for the server or servlets? (In a standalone Java application the code works fine for accessing the Google Drive (ie I have all the needed jars in that Java Application environment -- something is odd in the server (I think that is the Jetty server).


Update How DevAppServer finds classes

(As Mark Doyle points out) the DevAppServer's class loading scheme is wholly different for loading classes used by the webapp from the class loading scheme used to load the DevAppServer code itself -- And the web app class loading scheme is not the usual Eclipse project buildpath process. (I'm documenting how all this works here, for clarity, and so I can find it again.)

Looking into it I see that when loading classes for a webapp, DevAppServer calls a Google class 'IsolatedAppClassLoader' (to isolate the class loading of each webapp from each other. IsolatedAppClassLoader.loadClass() calls DevAppServerClassLoader.loadClass(), which (in most cases) eventually calls its superclass java.lang.ClassLoader.loadClass(), which, in this case calls java.net.URLClassLoader.findClass() which calls URLClassPath.getResource() which is where much of the work begins.

URLClassPath.getResource() has a 'for' loop which gets each loader listed in the URLClassPath object. In this case there are 17 (!seventeen) loaders in for the URLClassPath, all of the instances of sun.misc.URLClassPath$JarLoader (subclass of URLClassPath). -- These seem to be DevAppServer jars. My class is not found, a ClassNotFoundException is returned (at this level).

IsolatedAppClassLoader does this again (I don't understand why, but this time directly with the URLClassLoader object (the run() method), something about PrivilegedExceptionAction.run()??)) In any event, URLClassPath (from an anonymous subclass of URLClassLoader ??) runs URLClassPath.getResource(), (this the IsolatedAppClassLoader instance is a subclass of URLClassLoader!). The IsolatedAppClassLoader instance contains a 'ucp' field (URLClassPath sub object) with 21 loaders.

The first is a FileLoader instance (subclass of URLClassPath) which contains the url file: /C:/eclip/webAppName/war/WEB-INF/classes/ -- so this is where it finds all the individual classes in the war/WEB-INF/classes directory. The remaining 20 loaders are all JarLoader loaders. Of that 15 correspond to the 15 .jar files in the project's war/WEB-INF directory. The remaining 5 JarLoader instances are:

file:/C:/app/eclipse/plugins/com.google.appengine.eclipse.sdkbundle_1.8.2/appengine-java-sdk-1.8.2/lib/impl/agent/appengine-agentruntime.jar!/ file:/C:/app/eclipse/plugins/com.google.appengine.eclipse.sdkbundle_1.8.2/appengine-java-sdk-1.8.2/lib/tools/jsp/repackaged-appengine-jakarta-jstl-1.1.2.jar!/ file:/C:/app/eclipse/plugins/com.google.appengine.eclipse.sdkbundle_1.8.2/appengine-java-sdk-1.8.2/lib/tools/jsp/repackaged-appengine-jakarta-standard-1.1.2.jar!/ file:/C:/app/eclipse/plugins/com.google.appengine.eclipse.sdkbundle_1.8.2/appengine-java-sdk-1.8.2/lib/tools/jsp/repackaged-appengine-jasper-jdt-6.0.29.jar!/ file:/C:/app/eclipse/plugins/com.google.appengine.eclipse.sdkbundle_1.8.2/appengine-java-sdk-1.8.2/lib/opt/tools/appengine-local-endpoints/v1/appengine-local-endpoints.jar!/

I don't know why these are on the list, probably some standard stuff for Jetty?

Upvotes: 0

Views: 247

Answers (1)

Mark Doyle
Mark Doyle

Reputation: 4874

The GAE development server (Jetty) sets it's own classpath and it doesn't pay attention to what you've configured in Eclipse's build path. That classpath includes WEB-INF/lib and so you'll need to make sure that any libraries you want to use are located there, similarly classes from your project are typically built to WEB-INF/classes. It also includes the required GAE libraries and dependencies required to emulate the production environment.

In addition GAE development environment also provides it's own class loading mechanism. This provides for access to GAE libraries and also implements restrictions to what classes can be accessed which mimics the production environment. As an example many Java IO/graphics classes are restricted and can't be used.

With regard to exports and packaging to a war file. GAE SDK doesn't work the way you might expect if you use Eclipse's WTP for web development. When launching it simply starts it's custom Jetty instance and points to the web folder. Thus if you didn't put your jar libs in the WEB-INF/lib (similarly if your classes didn't compile to WEB-INF/classes) then they won't be found.

Upvotes: 1

Related Questions