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
- Unwrap e.getCause() to get the original cleanup exception
- Add the cleanup exception's class (e.g. IOException.class) to runSafely's declared exception classes
- Make the finallyBlock swallow cleanup errors (log and continue) so they never propagate
- 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
- Include cleanup-related exception classes (e.g. IOException) in runSafely's declared exceptions
- Log-and-continue inside finallyBlock instead of letting cleanup errors propagate
- Use closeQuietly-style helpers for cleanup that should not mask primary results
- Remember: a finally exception with a primary failure becomes suppressed, not wrapped
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
- Unknown throwable
- Suppressing failure in finally block
- Can't retrieve values from an empty struct
- Can't modify an empty struct
- %s doesn't implement cleanupLevel
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/5dc85ed0b5d13867.
Report an issue: GitHub.