JDK-8202620 : 5.3: Clarify handling of exceptions thrown by ClassLoaders
  • Type: Bug
  • Component: specification
  • Sub-Component: vm
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2018-05-03
  • Updated: 2023-12-20
  • Resolved: 2023-12-20
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 17
17Fixed
Related Reports
Relates :  
Relates :  
Description
The ClassLoader.loadClass method allows arbitrary user-defined behavior to occur during loading of classes. Notably, the method can throw any exception type, including RuntimeExceptions and Errors.

5.3 claims that, if any error occurs, "an instance of a subclass of LinkageError" may be thrown. It does not account for the possibility of exceptions that are not LinkageErrors.

The following test confirms that a RuntimeException thrown by ClassLoader.loadClass is exposed directly by class resolution.

~~~

public class Test {
   public static void main(String... args) throws Exception {
       PreemptingClassLoader l = new PreemptingClassLoader("A", "B");
       Class<?> c = l.loadClass("A");
       Runnable r = (Runnable) c.getDeclaredConstructor().newInstance();
       r.run();
   }
}

~~~

public class A implements Runnable {
   public void run() {
       System.out.println(B.foo);
   }
}

~~~

public class B {
   public static String foo = "foo";
}

~~~

import java.util.*;
import java.io.*;

public class PreemptingClassLoader extends ClassLoader {

 private final Set<String> names = new HashSet<>();

 public PreemptingClassLoader(String... names) {
   for (String n : names) this.names.add(n);
 }

 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
   if (!names.contains(name)) return super.loadClass(name, resolve);
   Class<?> result = findLoadedClass(name);
   if (result == null) {
     if (name.equals("B")) throw new RuntimeException("Hi, mom"); // INSERTED TO TRIGGER EXCEPTION
     String filename = name.replace('.', '/') + ".class";
     try (InputStream data = getResourceAsStream(filename)) {
       if (data == null) throw new ClassNotFoundException();
       try (ByteArrayOutputStream buffer = new ByteArrayOutputStream()) {
         int b;
         do {
           b = data.read();
           if (b >= 0) buffer.write(b);
         } while (b >= 0);
         byte[] bytes = buffer.toByteArray();
         result = defineClass(name, bytes, 0, bytes.length);
       }
     }
     catch (IOException e) {
       throw new ClassNotFoundException("Error reading class file", e);
     }
   }
   if (resolve) resolveClass(result);
   return result;
 }

}
Comments
This was addressed by Sealed Classes in JDK-8260516.
20-12-2023

Observation of Hotspot behavior from a slightly different test: during verification, RuntimeExceptions thrown when loading are also left untouched (except that ClassNotFoundException becomes NoClassDefFoundError, as specified).
03-05-2018

Two ways to go: 1) Massage the spec to allow for arbitrary exceptions being thrown. Includes changes to 5.3 (replace "instance of a subclass of LinkageError" with "exception"), 5.4.1 (verification can fail because of any exception), and 5.4.3 (resolution can fail because of any exception). 2) Specify in 5.3 that any exceptions that are not Errors are to be wrapped in (probably) a NoClassDefFoundError. Update Hotspot behavior. Not clear which is better. (2) is an incompatible change, but is more in the spirit of the JVM design (compare the treatment of ClassNotFoundException and exceptions thrown by bootstrap methods). Quite possibly there are no users who depend on a particular RuntimeException being thrown when class loading fails.
03-05-2018

This rule could use some scrutiny, too: "If the Java Virtual Machine ever attempts to load a class C during verification (§5.4.1) or resolution (§5.4.3) (but not initialization (§5.5)), and the class loader that is used to initiate loading of C throws an instance of ClassNotFoundException, then the Java Virtual Machine must throw an instance of NoClassDefFoundError whose cause is the instance of ClassNotFoundException." It's not clear to me under what circumstances initialization would trigger class loading, or why that case deserves special treatment. A clearer rule would simply state that _all_ exceptions that are not Errors get wrapped in NoClassDefFoundErrors.
03-05-2018