1

UPDATE 1: Indeed, the URL format differences cause the error. Here is a unit test (cut and obfuscated by hand; hope I didn't miss anything) showing the problem:

@Test
public void wheresWaldo2() throws ClassNotFoundException, IllegalAccessException, InstantiationException, IOException, NoSuchMethodException {
  // Find Waldo from file:/someLocation/waldo.jar.  Prove that works.
  URL waldosJar = new File("/someLocation/waldo.jar").toURI().toURL();
  assertNotNull(waldosJar);
  assertEquals("file", waldosJar.getProtocol());
  String waldosPath = waldosJar.getPath();
  assertNotNull(waldosPath);
  assertTrue(waldosPath.endsWith("/waldo.jar"));

  ClassLoader cl = new URLClassLoader(new URL[] { waldosJar }, this.getClass().getClassLoader());

  Class<?> waldosClass = cl.loadClass("com.foobar.Waldo");
  assertNotNull(waldosClass);
  assertEquals("com.foobar.Waldo", waldosClass.getName());
  assertSame(cl, waldosClass.getClassLoader());

  Class<?> jimbosClass = cl.loadClass("com.foobar.Jimbo"); // Note: works
  assertNotNull(jimbosClass);

  // Find Waldo from jar:file:/someLocation/waldo.jar!/.  Prove that works.
  // This URL, when passed to a URLClassLoader, should result in the same
  // functionality as the first one.  But it doesn't.
  waldosJar = new URL("jar:" + waldosJar.toExternalForm() + "!/");
  assertEquals("jar", waldosJar.getProtocol());
  assertEquals("file:" + waldosPath + "!/", waldosJar.getPath());

  cl = new URLClassLoader(new URL[] { waldosJar }, this.getClass().getClassLoader());

  waldosClass = cl.loadClass("com.foobar.Waldo");
  assertNotNull(waldosClass);
  assertEquals("com.foobar.Waldo", waldosClass.getName());
  assertSame(cl, waldosClass.getClassLoader());

  jimbosClass = cl.loadClass("com.foobar.Jimbo"); // XXX FAILS
}

UPDATE 0: The problem may have to do with the supposed, but not actual, equivalence between two URLs referring to a jar file. For example, the following two URLs are supposed to refer to the same file:

  • file:/myjar.jar
  • jar:file:/myjar.jar!/

When I pass a URL built from the first format to my machinery, I think things are working. When I pass a URL built from the second format, I get the results described below. I am testing more to confirm all this beyond a doubt.

ORIGINAL QUESTION

(I am aware of this question.)

I have a jar file, waldo.jar, that contains a META-INF/MANIFEST.MF that looks like this:

Manifest-Version: 1.0
Class-Path: jimbo.jar

It also has a class at the following location within it:

com/foobar/Waldo.class

The class' source code is essentially:

package com.foobar;

public class Waldo {
  public Jimbo getJimbo() {
    return null;
  }
}

Next, in the same directory, I have a jar file, jimbo.jar, that contains a class at the following location within it:

com/foobar/Jimbo.class

That class' source code is essentially:

package com.foobar;

public class Jimbo {

}

Now I construct a URLClassLoader with a URL to waldo.jar. To review: jimbo.jar contains Jimbo and is "next to" waldo.jar, and is listed in waldo.jar's META-INF/MANIFEST-MF's Class-Path header appropriately. waldo.jar contains Waldo, that has a code reference to Jimbo. With me so far?

I can load com.foobar.Waldo just fine. But if I do something with Waldo that involves com.foobar.Jimbo, like, for example, calling Waldo.class.getDeclaredMethod("getJimbo"), I get a NoClassDefFoundError. Here is a sample stack:

java.lang.NoClassDefFoundError: com/foobar/Jimbo
    at java.lang.Class.getDeclaredMethods0(Native Method)
    at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
    at java.lang.Class.getDeclaredMethod(Class.java:2128)
    at com.foobar.TestClassLoadingProblems.wheresWaldo(TestClassLoadingProblems.java:115)

    Caused by:
    java.lang.ClassNotFoundException: com.foobar.Jimbo
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at com.foobar.MyClassLoader.findClass(...) // calls super.findClass()
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)

This suggests to me that URLClassLoader is not consulting the Class-Path header properly in all cases (I'm aware of how it works in general).

Can anyone shed light on what is happening here?

Community
  • 1
  • 1
Laird Nelson
  • 15,321
  • 19
  • 73
  • 127
  • If instead of `Waldo.class.getDeclaredMethod("getJimbo")` you simply try to `new Jimbo()`, what is the result? – Adrian Colomitchi Oct 06 '16 at 22:29
  • I certainly can't do `new Jimbo()`, because the class that is using the classloader doesn't know about `Jimbo`. `jimbosClass.newInstance()` will almost assuredly fail. I'll rejigger my test to check it out. Stay tuned, though; the problem seems to be with the URL formats supplied to `URLClassLoader`: if I use `file:/myjar.jar` instead of `jar:file:/myjar.jar!/` (they're supposed to be equivalent), everything appears to work. – Laird Nelson Oct 06 '16 at 22:41
  • Ok I think this jar is manually generated. You are missing entries for the folders containing the class that's it. Just add entries(kind of index for file structure) to the jar in it will work. – user1615664 Oct 06 '16 at 22:52
  • Hi, no, that is certainly not the problem. Thanks for your comment, though. – Laird Nelson Oct 06 '16 at 22:55

1 Answers1

0

This is due to a bug in the JDK.

Laird Nelson
  • 15,321
  • 19
  • 73
  • 127