Repository: apache/parquet-java (2026-07-03T13:04:26Z)

Issue:
Handle NoClassDefFoundError in Dyn* classes
### Describe the bug, including details regarding any error messages, version, and platform.

All of the `Dyn*` classes use `Class.forName(className, true, loader)` and catch `ClassNotFoundException` when probing for optional implementations by class name, but `Class.forName` can also throw `NoClassDefFoundError` when the class itself is found but one of its transitive dependencies is missing. It can also throw `ExceptionInInitializerError` when the static initializer of the loaded class fails. 
We're intentionally not handling `ExceptionInInitializerError` and propagate it to the caller.

`DynConstructors` already handles `ClassNotFoundException` and `NoClassDefFoundError` while `DynMethods` only handles `ClassNotFoundException`

This issue is similar to https://github.com/apache/iceberg/pull/16793 and https://github.com/apache/iceberg/pull/16611 that we're fixing on the Iceberg side.

### Component(s)

Core


Discussion:

Patch:
diff --git a/parquet-common/src/main/java/org/apache/parquet/util/DynMethods.java b/parquet-common/src/main/java/org/apache/parquet/util/DynMethods.java
index 082f058e66..0d07283298 100644
--- a/parquet-common/src/main/java/org/apache/parquet/util/DynMethods.java
+++ b/parquet-common/src/main/java/org/apache/parquet/util/DynMethods.java
@@ -243,7 +243,7 @@ public Builder impl(String className, String methodName, Class<?>... argClasses)
       try {
         Class<?> targetClass = Class.forName(className, true, loader);
         impl(targetClass, methodName, argClasses);
-      } catch (ClassNotFoundException e) {
+      } catch (ClassNotFoundException | NoClassDefFoundError e) {
         // class not found on supplied classloader.
         LOG.debug("failed to load class {}", className, e);
       }
@@ -352,7 +352,7 @@ public Builder hiddenImpl(String className, String methodName, Class<?>... argCl
       try {
         Class<?> targetClass = Class.forName(className, true, loader);
         hiddenImpl(targetClass, methodName, argClasses);
-      } catch (ClassNotFoundException e) {
+      } catch (ClassNotFoundException | NoClassDefFoundError e) {
         // class not found on supplied classloader.
         LOG.debug("failed to load class {}", className, e);
       }


Test patch:
diff --git a/parquet-common/src/test/java/org/apache/parquet/util/TestDynConstructors.java b/parquet-common/src/test/java/org/apache/parquet/util/TestDynConstructors.java
index 26124b9c81..9327660f88 100644
--- a/parquet-common/src/test/java/org/apache/parquet/util/TestDynConstructors.java
+++ b/parquet-common/src/test/java/org/apache/parquet/util/TestDynConstructors.java
@@ -19,6 +19,9 @@
 
 package org.apache.parquet.util;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
 import java.util.concurrent.Callable;
 import org.apache.parquet.TestUtils;
 import org.apache.parquet.util.Concatenator.SomeCheckedException;
@@ -166,4 +169,63 @@ public void testInvoke() throws Exception {
     Assert.assertNotNull("Should allow invokeChecked(null, ...)", ctor.invokeChecked(null));
     Assert.assertNotNull("Should allow invoke(null, ...)", ctor.invoke(null));
   }
+
+  @Test
+  public void implWithNoClassDefFoundError() throws Exception {
+    ClassLoader errorLoader = new ClassLoader(Thread.currentThread().getContextClassLoader()) {
+      @Override
+      public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        if ("org.apache.parquet.MissingDependencyClass".equals(name)) {
+          throw new NoClassDefFoundError("some/TransitiveDependency");
+        }
+
+        return super.loadClass(name, resolve);
+      }
+    };
+
+    assertThatThrownBy(() -> new DynConstructors.Builder(MyInterface.class)
+            .loader(errorLoader)
+            .impl("org.apache.parquet.MissingDependencyClass")
+            .buildChecked())
+        .isInstanceOf(NoSuchMethodException.class)
+        .hasMessageStartingWith("Cannot find constructor for interface")
+        .hasMessageContaining("Missing org.apache.parquet.MissingDependencyClass");
+
+    assertThat(new DynConstructors.Builder(MyInterface.class)
+            .loader(errorLoader)
+            .impl("org.apache.parquet.MissingDependencyClass")
+            .impl(MyClass.class)
+            .buildChecked()
+            .newInstance())
+        .isInstanceOf(MyClass.class);
+
+    assertThat(new DynConstructors.Builder(MyInterface.class)
+            .loader(errorLoader)
+            .hiddenImpl("org.apache.parquet.MissingDependencyClass")
+            .impl(MyClass.class)
+            .buildChecked()
+            .newInstance())
+        .isInstanceOf(MyClass.class);
+  }
+
+  @Test
+  public void implWithExceptionInInitializerError() {
+    ClassLoader errorLoader = new ClassLoader(Thread.currentThread().getContextClassLoader()) {
+      @Override
+      public Class<?> loadClass(String name, boolean resolve) {
+        throw new ExceptionInInitializerError("static initializer failed");
+      }
+    };
+
+    assertThatThrownBy(() -> new DynConstructors.Builder(MyInterface.class)
+            .loader(errorLoader)
+            .impl("org.apache.parquet.FailingInitClass")
+            .buildChecked())
+        .isInstanceOf(ExceptionInInitializerError.class)
+        .hasMessage("static initializer failed");
+  }
+
+  public interface MyInterface {}
+
+  public static class MyClass implements MyInterface {}
 }
diff --git a/parquet-common/src/test/java/org/apache/parquet/util/TestDynMethods.java b/parquet-common/src/test/java/org/apache/parquet/util/TestDynMethods.java
index 94e9ed93a9..59c6d37167 100644
--- a/parquet-common/src/test/java/org/apache/parquet/util/TestDynMethods.java
+++ b/parquet-common/src/test/java/org/apache/parquet/util/TestDynMethods.java
@@ -19,6 +19,9 @@
 
 package org.apache.parquet.util;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
 import java.util.concurrent.Callable;
 import org.apache.parquet.TestUtils;
 import org.apache.parquet.util.Concatenator.SomeCheckedException;
@@ -306,4 +309,69 @@ public void testNoop() throws Exception {
     Assert.assertNull("NOOP can be bound to null", noop.bind(null).invoke("a"));
     Assert.assertNull("NOOP can be static", noop.asStatic().invoke("a"));
   }
+
+  static class Available {
+    public static String register() {
+      return "available";
+    }
+
+    @SuppressWarnings("unused")
+    private static String hiddenRegister() {
+      return "hidden-available";
+    }
+  }
+
+  @Test
+  public void implWithNoClassDefFoundError() throws NoSuchMethodException {
+    ClassLoader errorLoader = new ClassLoader(Thread.currentThread().getContextClassLoader()) {
+      @Override
+      public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        if ("org.apache.parquet.MissingDependencyClass".equals(name)) {
+          throw new NoClassDefFoundError("some/TransitiveDependency");
+        }
+
+        return super.loadClass(name, resolve);
+      }
+    };
+
+    assertThatThrownBy(() -> new DynMethods.Builder("register")
+            .loader(errorLoader)
+            .impl("org.apache.parquet.MissingDependencyClass")
+            .buildStaticChecked())
+        .isInstanceOf(NoSuchMethodException.class)
+        .hasMessage("Cannot find method: register");
+
+    assertThat(new DynMethods.Builder("register")
+            .loader(errorLoader)
+            .impl("org.apache.parquet.MissingDependencyClass")
+            .impl(Available.class)
+            .buildStaticChecked()
+            .<String>invoke())
+        .isEqualTo("available");
+
+    assertThat(new DynMethods.Builder("register")
+            .loader(errorLoader)
+            .hiddenImpl("org.apache.parquet.MissingDependencyClass")
+            .hiddenImpl(Available.class, "hiddenRegister")
+            .buildStaticChecked()
+            .<String>invoke())
+        .isEqualTo("hidden-available");
+  }
+
+  @Test
+  public void implWithExceptionInInitializerError() {
+    ClassLoader errorLoader = new ClassLoader(Thread.currentThread().getContextClassLoader()) {
+      @Override
+      public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        throw new ExceptionInInitializerError("static initializer failed");
+      }
+    };
+
+    assertThatThrownBy(() -> new DynMethods.Builder("register")
+            .loader(errorLoader)
+            .impl("org.apache.parquet.FailingInitClass")
+            .buildStaticChecked())
+        .isInstanceOf(ExceptionInInitializerError.class)
+        .hasMessage("static initializer failed");
+  }
 }