I'm trying to unmarshall an XML file into a generated class structure using JAXB in java. I'm running into a perplexing issue where the classloader I hand into JAXBContext.newInstance(packageName, classLoader)
can apparently not find some of the necessary classes to instantiate the schema classes, but when I manually search the supplied classloader for the needed classes, they are there:
URLClassLoader cl = this.getJaxbClassloader();
try
{
cl.loadClass("org.postgresql.util.PGInterval");
Log.error("Found class [" + name + "] in provided classloader");
}
catch (ClassNotFoundException e)
{
Log.error("Unable to find class [" + name + "] in provided classloader");
}
JAXBContext ctx = JAXBContext.newInstance( "com.comp.gen", cl);
The getJaxbClassloader()
method just creates a new URLClassLoader, loading up some specific jars needed by the generated classes, and then setting the system classloader as the parent. The generated classes use some postgresql libraries that I put into the classloader, which is the resource I'm having the issue with. JAXB correctly finds the ObjectFactory class in the supplied package, it's just the instantiation of the generated classes themselves that seem be the problem.
The result of running this code is that the manual call of cl.loadClass("org.postgresql.util.PGInterval");
works fine, it logs the the first statement saying that it found the class, no exceptions thrown. But when the JAXBContext
is instantiated, it throws a CNFE on the exact same resource:
java.lang.ClassNotFoundException: org.postgresql.util.PGInterval
at java.net.URLClassLoader.findClass(URLClassLoader.java:600)
at java.lang.ClassLoader.loadClassHelper(ClassLoader.java:772)
at java.lang.ClassLoader.loadClass(ClassLoader.java:745)
at java.lang.ClassLoader.loadClass(ClassLoader.java:726)
... 78 more
More Thorough stack trace:
java.lang.NoClassDefFoundError: org.postgresql.util.PGInterval
at java.lang.Class.getDeclaredFieldsImpl(Native Method)
at java.lang.Class.getDeclaredFields(Class.java:740)
at com.sun.xml.bind.v2.model.nav.ReflectionNavigator.getDeclaredFields(ReflectionNavigator.java:249)
at com.sun.xml.bind.v2.model.nav.ReflectionNavigator.getDeclaredFields(ReflectionNavigator.java:58)
at com.sun.xml.bind.v2.model.impl.ClassInfoImpl.findFieldProperties(ClassInfoImpl.java:370)
at com.sun.xml.bind.v2.model.impl.RuntimeClassInfoImpl.getProperties(RuntimeClassInfoImpl.java:176)
at com.sun.xml.bind.v2.model.impl.ModelBuilder.getClassInfo(ModelBuilder.java:243)
at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.getClassInfo(RuntimeModelBuilder.java:100)
at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.getClassInfo(RuntimeModelBuilder.java:81)
at com.sun.xml.bind.v2.model.impl.ModelBuilder.getClassInfo(ModelBuilder.java:209)
at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.getClassInfo(RuntimeModelBuilder.java:95)
at com.sun.xml.bind.v2.model.impl.RuntimeModelBuilder.getClassInfo(RuntimeModelBuilder.java:81)
at com.sun.xml.bind.v2.model.impl.ModelBuilder.getTypeInfo(ModelBuilder.java:315)
at com.sun.xml.bind.v2.model.impl.RegistryInfoImpl.<init>(RegistryInfoImpl.java:99)
at com.sun.xml.bind.v2.model.impl.ModelBuilder.addRegistry(ModelBuilder.java:357)
at com.sun.xml.bind.v2.model.impl.ModelBuilder.getTypeInfo(ModelBuilder.java:327)
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(JAXBContextImpl.java:466)
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:302)
at com.sun.xml.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(JAXBContextImpl.java:1136)
at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:154)
at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:121)
at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:202)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:95)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:56)
at java.lang.reflect.Method.invoke(Method.java:620)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:184)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:144)
at javax.xml.bind.ContextFinder.find(ContextFinder.java:346)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:443)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:406)
Anyone have an idea as to what's going wrong here? I was under the impression (and the JAXBContext docs support this) that it would use the supplied classloader to find an implementation classes needed to instantiate the classes, so given that the resource appears to be within the classloader I'm supplying, why is JAXB unable to find it?
EDIT: Adding the relevant portion of the generated class that uses the PGInterval resource:
import org.postgresql.util.PGInterval;
...
...
...
@XmlElement(name = "time_to_live", required=false)
protected PGInterval time_to_live;
public PGInterval gettime_to_live()
{
return time_to_live;
}
public void settime_to_live(PGInterval time_to_live)
{
this.time_to_live = time_to_live;
}
I guess it's worth noting that this this the only import in the generated class is that is not in java's standard library.
So I eventually figured this out. Deep within the code-base I was working in, a prior developer had created a custom dynamic classloader that used ClassLoader.getSystemClassLoader() as its parent. This custom classloader is actually used to load up the generated JAXB class from disk, and I was using that instance's classloader for the JAXB stuff. Once I saw that I instantly knew the problem.
The environment I was running in was a restapi on top of tomcat, so the system-classloader at my component level only contained the catalina jars required to bootstrap tomcat.
The prior developer only ever ran his code in a standalone JVM, where he provided a huge classpath. So although this was technically an environmental problem, the root cause was the usage of ClassLoader.getSystemClassLoader() as the parent of a new ClassLoader. Changing the parent to be something more logical, ie the containing class' classloader solved the problem.