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