I'm making an API wrapper library that supports multiple versions of the API. A public class was added in the recent API version. I'm trying to compile the wrapper against the latest API version and make it check in run time if the new class exists. And I'm trying to avoid reflection and instead catch NoClassDefFoundError
and set a flag accordingly.
It works until I add a method that returns the class which the non-existent class extends. Then my library fails to load. I mean:
BaseClass
exists; ChildClass
does not exist; the method uses ChildClass
internally. If the method returns BaseClass
the library fails to load. If the method returns Object
the library loads and the error is deferred and can be caught.
What part of the standard describes this behavior?
Here's a minimal example:
public class TestLoading {
public static void main(String[] args) throws Exception {
Class.forName(BaseClass.class.getName());
// Class.forName(B.class.getName())
URL classFileB =
TestLoading.class.getResource(TestLoading.class.getSimpleName() + "$ChildClass.class");
if (classFileB != null) {
if (!"file".equals(classFileB.getProtocol())) {
throw new UnsupportedOperationException();
}
Path path = new File(classFileB.getPath()).toPath();
System.out.println("deleting: " + path);
Files.delete(path);
}
loadMyClass(ObjectReturner.class.getName());
loadMyClass(BaseClassReturner.class.getName());
}
private static void loadMyClass(String name) throws ClassNotFoundException {
System.out.println("loading: " + name + "...");
try {
Class.forName(name);
} catch (Throwable e) {
e.printStackTrace(System.out);
}
}
public static class BaseClass {
static {
System.out.println("loaded: " + BaseClass.class.getName());
}
}
public static class ChildClass extends BaseClass {
static {
System.out.println("loaded: " + ChildClass.class.getName());
}
}
public static class ObjectReturner {
static {
System.out.println("loaded: " + ObjectReturner.class.getName());
}
public Object getObject() {
return new ChildClass();
}
}
public static class BaseClassReturner {
static {
System.out.println("loaded: " + BaseClassReturner.class.getName());
}
public BaseClass getObject() {
if ("".length() == 10) {
return new ChildClass();
} else {
return null;
}
}
}
}
program output
loaded: snippet.TestLoading$BaseClass
deleting: C:\keep\eclipse\formendix\_pasted_code_\target\classes\snippet\TestLoading$ChildClass.class
loading: snippet.TestLoading$ObjectReturner...
loaded: snippet.TestLoading$ObjectReturner
loading: snippet.TestLoading$BaseClassReturner...
java.lang.NoClassDefFoundError: snippet/TestLoading$ChildClass
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:377)
at snippet.TestLoading.loadMyClass(TestLoading.java:31)
at snippet.TestLoading.main(TestLoading.java:25)
Caused by: java.lang.ClassNotFoundException: snippet.TestLoading$ChildClass
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:606)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:168)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
... 4 more