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;