Search code examples
javaclassloader

NoClassDefFoundError - Dynamic loading Classpath Class dependency loaded in URLClassLoader


Disclaimer: I'm aware there are a fair amount of questions on SO about classloading, but I haven't found a soluation yet...

I have a class on my classpath that has a dependency on a dynamically loaded class. Now I could load that class on java 8 but on later versions its kind of an issue.

On my classpath I have the c:/git/udevelop91/JAVA/usoft.jar That contains the com.usoft.birt.ReportEngine class. For this class to work I need the jars in c:/ReportEngine/lib. One of these jars contains the PlatformConfig. When I load the platformconfig seperatly it works fine and the class gets resolved. When I try to load the ReportEngine class however that fails. Because it has a dependency on platformconfig in the LibraryClassLoader. Ive added the usoft.jar to the LibraryClassLoader as well to see if that works but it seems like that it keeps using the AppClassLoader and not my LibraryClassLoader resulting in a NoClassDefFoundError. How can I solve this without removing the usoft.jar from the classpath and without adding all the birt jars to the classpath? So the line with reportEngineInst fails

public void loadIt(){
    File f = new File("C:/ReportEngine/lib");
    File[] files = f.isDirectory() ? f.listFiles() : new File[] {f};
    URL[] urlss = new URL[files.length];
    int i = 0;
    for(File ff : files){
        urlss[i++] = ff.toURI().toURL();
    }

    LibraryClassLoader urlloader = new LibraryClassLoader(urlss, ClassLoader.getSystemClassLoader());   
    urlloader.addJar("c:/git/udev/JAVA/USoft.jar");

    Class<?> platformLoader = Class.forName("org.eclipse.birt.core.framework.PlatformConfig",true,urlloader);
    Class<?> report = urlloader.loadClass("com.usoft.birt.ReportEngine");
    // ---------- The line below fails with a NoClassDefFoundError ----------
    Object reportEngineInst = report.getDeclaredConstructor(String.class, String.class).newInstance("c:/ReportEngine", "c:/ReportEngine");
}

public class LibraryClassLoader extends URLClassLoader{
    public LibraryClassLoader( URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }
    public void addJar(String path) throws MalformedURLException {
        super.addURL(Paths.get(path).toUri().toURL());
    }   
}

eclipse variables at runtime


Solution

  • The comments from Simon helped me realize that I couldn't use the SystemClassLoader. To keep using the current solution with the same classes on the classpath and add the specific classes later I now have:

    public void loadIt() throws Exception{
        /*Instantiate list for own classloader*/
        ArrayList<URL> urlss = new ArrayList<>();
    
        /*First fix the default classpath dependencies. usoft.jar is normally on the classpath*/
        for(String pathEntry : System.getProperty("java.class.path").split(System.getProperty("path.separator"))) {
            File fPathEntry = new File(pathEntry);
            File[] fPathEntries = fPathEntry.isDirectory() ? fPathEntry.listFiles() : new File[] {fPathEntry};
            for(File f : fPathEntries)
                urlss.add(f.toURI().toURL());
        }
    
        /*Now add the specific stuff*/
        File f = new File("C:/ReportEngine/lib");
        File[] files = f.isDirectory() ? f.listFiles() : new File[] {f};
        for(File ff : files)
            urlss.add(ff.toURI().toURL());
    
        /* 
           Make the classloader with the PlatformClassLoader as a parent 
           instead of the SystemClassLoader. I tried without a parent
           classloader, but then you cant find the platform classes 
           like java.sql.* 
        */
        URLClassLoader urlloader = new URLClassLoader(urlss.toArray(new URL[urlss.size()]), ClassLoader.getPlatformClassLoader());  
    
        /*And now it magically works*/
        Class<?> platformLoader = Class.forName("org.eclipse.birt.core.framework.PlatformConfig",true,urlloader);
        Class<?> report = Class.forName("com.usoft.birt.ReportEngine",true,urlloader);
        Object a = report.getDeclaredConstructor(String.class, String.class).newInstance("c:/reportEngine", "c:/reportEngine")  ;
        Method m = report.getMethod("generateDOC", String.class, String.class, Object[].class);
        m.invoke(a,"","",new Object[] {});
    }