apache/iceberg · warning · RuntimeException

Unknown exception in finally block

Error message

Unknown exception in finally block

What it means

ExceptionUtil.runSafely's finally-block fallback: when the finallyBlock throws an exception while the main block succeeded (failure == null) and that exception matches none of the declared exception classes or RuntimeException, it is wrapped in RuntimeException("Unknown exception in finally block"). If a primary failure existed, the finally exception is suppressed instead of wrapped.

Source

Thrown at api/src/main/java/org/apache/iceberg/util/ExceptionUtil.java:132

      tryThrowAs(failure, e2Class);
      tryThrowAs(failure, e3Class);
      tryThrowAs(failure, RuntimeException.class);
      throw new RuntimeException("Unknown throwable", failure);

    } finally {
      if (finallyBlock != null) {
        try {
          finallyBlock.run();
        } catch (Exception e) {
          if (failure != null) {
            LOG.warn("Suppressing failure in finally block", e);
            failure.addSuppressed(e);
          } else {
            tryThrowAs(e, e1Class);
            tryThrowAs(e, e2Class);
            tryThrowAs(e, e3Class);
            tryThrowAs(e, RuntimeException.class);
            throw new RuntimeException("Unknown exception in finally block", e);
          }
        }
      }
    }
  }

  private static <E extends Exception> void tryThrowAs(Throwable failure, Class<E> excClass)
      throws E {
    if (excClass.isInstance(failure)) {
      throw excClass.cast(failure);
    }
  }
}

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Unwrap e.getCause() to get the original cleanup exception
  2. Add the cleanup exception's class (e.g. IOException.class) to runSafely's declared exception classes
  3. Make the finallyBlock swallow cleanup errors (log and continue) so they never propagate
  4. Use IOUtils.closeQuietly-style helpers inside the finally block

Example fix

// before
ExceptionUtil.runSafely(block, catchB, () -> stream.close(), RuntimeException.class); // IOException wrapped
// after
ExceptionUtil.runSafely(block, catchB, () -> stream.close(), IOException.class, RuntimeException.class);
Defensive patterns

Strategy: try-catch

Try / catch

try {
  ExceptionUtil.runSafely(block, catchBlock, finallyBlock, E1.class, E2.class, E3.class);
} catch (RuntimeException e) {
  if ("Unknown exception in finally block".equals(e.getMessage()) && e.getCause() != null) {
    handleCleanupFailure(e.getCause());
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: A finallyBlock (e.g. resource cleanup like closing streams/files) throws a checked exception not declared in runSafely's exception class list, while the main block completed successfully.

Common situations: Cleanup code closing custom AutoCloseables that throw checked exceptions (e.g. IOException from a Closeable) when runSafely was parameterized only for other exception types.

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/5dc85ed0b5d13867. Report an issue: GitHub.