diff --git a/spring-core/src/main/java/org/springframework/aot/nativex/feature/ThrowawayClassLoader.java b/spring-core/src/main/java/org/springframework/aot/nativex/feature/ThrowawayClassLoader.java index 1d12a525a8bb..0fe6d7a1f2d2 100644 --- a/spring-core/src/main/java/org/springframework/aot/nativex/feature/ThrowawayClassLoader.java +++ b/spring-core/src/main/java/org/springframework/aot/nativex/feature/ThrowawayClassLoader.java @@ -54,7 +54,11 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE return super.loadClass(name, true); } catch (ClassNotFoundException ex) { - return loadClassFromResource(name); + Class loadedFromResource = loadClassFromResource(name); + if (loadedFromResource == null) { + throw ex; + } + return loadedFromResource; } } } diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/feature/ThrowawayClassLoaderTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/feature/ThrowawayClassLoaderTests.java index 71fe5732372a..9adaa688583a 100644 --- a/spring-core/src/test/java/org/springframework/aot/nativex/feature/ThrowawayClassLoaderTests.java +++ b/spring-core/src/test/java/org/springframework/aot/nativex/feature/ThrowawayClassLoaderTests.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link ThrowawayClassLoader}. @@ -56,6 +57,23 @@ public InputStream getResourceAsStream(String name) { assertThat(closed).as("InputStream closed").isTrue(); } + @Test + void loadClassThrowsClassNotFoundExceptionWhenClassResourceIsMissing() { + // The grandparent resolves bootstrap classes only, so super.loadClass(...) fails, + // and the resource loader provides no class bytes. The fallback must then honor the + // ClassLoader.loadClass contract by reporting the failure instead of returning null. + ClassLoader resourceLoader = new ClassLoader(new ClassLoader(null) {}) { + @Override + public InputStream getResourceAsStream(String name) { + return null; + } + }; + ThrowawayClassLoader classLoader = new ThrowawayClassLoader(resourceLoader); + + assertThatExceptionOfType(ClassNotFoundException.class) + .isThrownBy(() -> classLoader.loadClass("com.example.MissingClass")); + } + private static byte[] classBytesOf(String className) throws IOException { String resourceName = className.replace('.', '/') + ".class"; try (InputStream in = ThrowawayClassLoaderTests.class.getClassLoader().getResourceAsStream(resourceName)) {