You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by et...@apache.org on 2026/07/13 10:47:52 UTC

(iceberg) branch 1.11.x updated: Core: Handle ExceptionInInitializerError & NoClassDefFoundError invoke-time failures in DynMethods callers (#16611) (#17184)

This is an automated email from the ASF dual-hosted git repository.

nastra pushed a commit to branch 1.11.x
in repository https://gitbox.apache.org/repos/asf/iceberg.git


The following commit(s) were added to refs/heads/1.11.x by this push:
     new f560c99160 Core: Handle ExceptionInInitializerError & NoClassDefFoundError invoke-time failures in DynMethods callers (#16611) (#17184)
f560c99160 is described below

commit f560c991608746c987a2c01365ae9bd5a23bb724
Author: Eduard Tudenhoefner <et...@gmail.com>
AuthorDate: Mon Jul 13 12:47:46 2026 +0200

    Core: Handle ExceptionInInitializerError & NoClassDefFoundError invoke-time failures in DynMethods callers (#16611) (#17184)
---
 .../main/java/org/apache/iceberg/InternalData.java | 29 ++++++++++-------
 .../iceberg/formats/FormatModelRegistry.java       | 34 +++++++++++++------
 .../java/org/apache/iceberg/TestInternalData.java  | 36 ++++++++++++++++++++
 .../iceberg/formats/TestFormatModelRegistry.java   | 38 ++++++++++++++++++++++
 4 files changed, 116 insertions(+), 21 deletions(-)

diff --git a/core/src/main/java/org/apache/iceberg/InternalData.java b/core/src/main/java/org/apache/iceberg/InternalData.java
index 040905f032..ea5ef85b02 100644
--- a/core/src/main/java/org/apache/iceberg/InternalData.java
+++ b/core/src/main/java/org/apache/iceberg/InternalData.java
@@ -50,24 +50,31 @@ public class InternalData {
     READ_BUILDERS.put(format, readBuilder);
   }
 
-  @SuppressWarnings("CatchBlockLogException")
   private static void registerSupportedFormats() {
     InternalData.register(
         FileFormat.AVRO,
         outputFile -> Avro.write(outputFile).createWriterFunc(InternalWriter::create),
         inputFile -> Avro.read(inputFile).createResolvingReader(InternalReader::create));
 
-    try {
-      DynMethods.StaticMethod registerParquet =
-          DynMethods.builder("register")
-              .impl("org.apache.iceberg.InternalParquet")
-              .buildStaticChecked();
-
-      registerParquet.invoke();
+    register("org.apache.iceberg.InternalParquet");
+  }
 
-    } catch (NoSuchMethodException e) {
-      // failing to load Parquet is normal and does not require a stack trace
-      LOG.info("Unable to register Parquet for metadata files: {}", e.getMessage());
+  /**
+   * Invokes the static {@code register} method of the given class, tolerating the failure modes
+   * that occur when an optional module (like {@code iceberg-parquet}) is not on the classpath.
+   *
+   * <p>Besides {@link NoSuchMethodException}, invoking {@code register} can fail with {@link
+   * NoClassDefFoundError} when its body references a missing transitive dependency, or {@link
+   * ExceptionInInitializerError} when it triggers a failing static initializer.
+   */
+  @SuppressWarnings("CatchBlockLogException")
+  private static void register(String classToRegister) {
+    try {
+      DynMethods.builder("register").impl(classToRegister).buildStaticChecked().invoke();
+    } catch (NoSuchMethodException | NoClassDefFoundError | ExceptionInInitializerError e) {
+      // failing to load an optional format is normal and does not require a stack trace
+      Throwable cause = e.getCause() != null ? e.getCause() : e;
+      LOG.info("Cannot register {} for metadata files: {}", classToRegister, cause.toString());
     }
   }
 
diff --git a/core/src/main/java/org/apache/iceberg/formats/FormatModelRegistry.java b/core/src/main/java/org/apache/iceberg/formats/FormatModelRegistry.java
index e1e93aa1fd..8596a113a9 100644
--- a/core/src/main/java/org/apache/iceberg/formats/FormatModelRegistry.java
+++ b/core/src/main/java/org/apache/iceberg/formats/FormatModelRegistry.java
@@ -199,19 +199,33 @@ public final class FormatModelRegistry {
     return model;
   }
 
-  @SuppressWarnings("CatchBlockLogException")
   private static void registerSupportedFormats() {
     // Uses dynamic methods to call the `register` for the listed classes
     for (String classToRegister : CLASSES_TO_REGISTER) {
-      try {
-        DynMethods.builder("register").impl(classToRegister).buildStaticChecked().invoke();
-      } catch (NoSuchMethodException e) {
-        // failing to register a factory is normal and does not require a stack trace
-        LOG.info(
-            "Unable to call register for ({}). Check for missing jars on the classpath: {}",
-            classToRegister,
-            e.getMessage());
-      }
+      register(classToRegister);
+    }
+  }
+
+  /**
+   * Invokes the static {@code register} method of the given class, tolerating the failure modes
+   * that occur when an optional module (like {@code iceberg-parquet} or {@code iceberg-orc}) is not
+   * on the classpath.
+   *
+   * <p>Besides {@link NoSuchMethodException}, invoking {@code register} can fail with {@link
+   * NoClassDefFoundError} when its body references a missing transitive dependency, or {@link
+   * ExceptionInInitializerError} when it triggers a failing static initializer.
+   */
+  @SuppressWarnings("CatchBlockLogException")
+  private static void register(String classToRegister) {
+    try {
+      DynMethods.builder("register").impl(classToRegister).buildStaticChecked().invoke();
+    } catch (NoSuchMethodException | NoClassDefFoundError | ExceptionInInitializerError e) {
+      // failing to register a factory is normal and does not require a stack trace
+      Throwable cause = e.getCause() != null ? e.getCause() : e;
+      LOG.info(
+          "Unable to call register for ({}). Check for missing jars on the classpath: {}",
+          classToRegister,
+          cause.toString());
     }
   }
 }
diff --git a/core/src/test/java/org/apache/iceberg/TestInternalData.java b/core/src/test/java/org/apache/iceberg/TestInternalData.java
index ae1678e04d..d3ce117a4b 100644
--- a/core/src/test/java/org/apache/iceberg/TestInternalData.java
+++ b/core/src/test/java/org/apache/iceberg/TestInternalData.java
@@ -19,8 +19,10 @@
 package org.apache.iceberg;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNoException;
 
 import java.io.IOException;
+import java.lang.reflect.Method;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.List;
@@ -32,6 +34,7 @@ import org.apache.iceberg.io.InputFile;
 import org.apache.iceberg.io.OutputFile;
 import org.apache.iceberg.relocated.com.google.common.collect.Lists;
 import org.apache.iceberg.types.Types;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestTemplate;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.junit.jupiter.api.io.TempDir;
@@ -155,4 +158,37 @@ public class TestInternalData {
       }
     }
   }
+
+  @Test
+  public void registerToleratesMissingClass() {
+    assertThatNoException().isThrownBy(() -> register("org.apache.iceberg.DoesNotExist"));
+  }
+
+  @Test
+  public void registerToleratesNoClassDefFoundErrorOnInvoke() {
+    assertThatNoException().isThrownBy(() -> register(ThrowsOnInvoke.class.getName()));
+  }
+
+  @Test
+  public void registerToleratesExceptionInInitializerErrorOnInvoke() {
+    assertThatNoException().isThrownBy(() -> register(ThrowsInitErrorOnInvoke.class.getName()));
+  }
+
+  private static void register(String className) throws ReflectiveOperationException {
+    Method register = InternalData.class.getDeclaredMethod("register", String.class);
+    register.setAccessible(true);
+    register.invoke(null, className);
+  }
+
+  public static class ThrowsOnInvoke {
+    public static void register() {
+      throw new NoClassDefFoundError("some/missing/TransitiveDependency");
+    }
+  }
+
+  public static class ThrowsInitErrorOnInvoke {
+    public static void register() {
+      throw new ExceptionInInitializerError(new RuntimeException("lazy init failed"));
+    }
+  }
 }
diff --git a/core/src/test/java/org/apache/iceberg/formats/TestFormatModelRegistry.java b/core/src/test/java/org/apache/iceberg/formats/TestFormatModelRegistry.java
index fe7a4d96f6..cda302c249 100644
--- a/core/src/test/java/org/apache/iceberg/formats/TestFormatModelRegistry.java
+++ b/core/src/test/java/org/apache/iceberg/formats/TestFormatModelRegistry.java
@@ -18,9 +18,11 @@
  */
 package org.apache.iceberg.formats;
 
+import static org.assertj.core.api.Assertions.assertThatNoException;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
 
+import java.lang.reflect.Method;
 import org.apache.iceberg.FileFormat;
 import org.apache.iceberg.encryption.EncryptedOutputFile;
 import org.apache.iceberg.io.InputFile;
@@ -86,6 +88,42 @@ class TestFormatModelRegistry {
         .hasMessageContaining("Cannot register class");
   }
 
+  @Test
+  void registerToleratesMissingClass() {
+    assertThatNoException().isThrownBy(() -> register("org.apache.iceberg.formats.DoesNotExist"));
+    assertThat(FormatModelRegistry.models()).isEmpty();
+  }
+
+  @Test
+  void registerToleratesNoClassDefFoundErrorOnInvoke() {
+    assertThatNoException().isThrownBy(() -> register(ThrowsOnInvoke.class.getName()));
+    assertThat(FormatModelRegistry.models()).isEmpty();
+  }
+
+  @Test
+  void registerToleratesExceptionInInitializerErrorOnInvoke() {
+    assertThatNoException().isThrownBy(() -> register(ThrowsInitErrorOnInvoke.class.getName()));
+    assertThat(FormatModelRegistry.models()).isEmpty();
+  }
+
+  private static void register(String className) throws ReflectiveOperationException {
+    Method register = FormatModelRegistry.class.getDeclaredMethod("register", String.class);
+    register.setAccessible(true);
+    register.invoke(null, className);
+  }
+
+  public static class ThrowsOnInvoke {
+    public static void register() {
+      throw new NoClassDefFoundError("some/missing/TransitiveDependency");
+    }
+  }
+
+  public static class ThrowsInitErrorOnInvoke {
+    public static void register() {
+      throw new ExceptionInInitializerError(new RuntimeException("lazy init failed"));
+    }
+  }
+
   private static class DummyParquetFormatModel implements FormatModel<Object, Object> {
     private final Class<?> type;
     private final Class<?> schemaType;