apache/iceberg · error · RuntimeException
No other bound is defined.
Error message
No other bound is defined.
What it means
OrcMetrics.truncateIfNeeded truncates ORC string statistics to an Iceberg bound (BELOW/ABOVE) when converting min/max stats to Iceberg literals. The switch over the LowerBound/UpperBound enum has cases for LOWER and UPPER only; any other value falls into the default branch and throws. Since only two bounds exist, this indicates a programming error or a future enum value not yet handled.
Solutions
- Upgrade to a current Iceberg version where the bound switch is complete; this path is only reachable with a mismatched or patched build.
- Check that you are not mixing iceberg-orc and iceberg-core JARs from different versions on the classpath.
- If you maintain a fork, add a case for the new Bound constant (or replace RuntimeException with UnsupportedOperationException for consistency) and a unit test covering both bounds.
Example fix
// before
default:
throw new RuntimeException("No other bound is defined.");
// after
default:
throw new UnsupportedOperationException("Unhandled bound: " + bound);
// or add the missing case:
case NEW_BOUND:
return ...; Defensive patterns
Strategy: type-guard
Validate before calling
if (bound != Bound.Is.LOWER && bound != Bound.Is.UPPER) { throw new IllegalArgumentException("Unsupported bound: " + bound); } Type guard
static boolean isSupportedBound(org.apache.iceberg.expressions.Bound<?> b) { return b instanceof org.apache.iceberg.expressions.Bound.Is; } Try / catch
try { stats = OrcMetrics.fromOrcMin(...); } catch (RuntimeException e) { if (e.getMessage().contains("No other bound")) { skipStats(); } else throw e; } Prevention
- Keep iceberg-orc and iceberg-core at the same version
- Never patch Bound enums in forks without updating OrcMetrics
- Add exhaustive-switch tests covering both bounds
When it happens
Trigger: Calling fromOrcMin or fromOrcMax on OrcMetrics with a CharSequence stat whose bound value is neither Bound.Is BELOW(lower) case nor UPPER case — i.e. a new/unmapped Bound enum constant reaching truncateIfNeeded's default branch.
Common situations: Iceberg version drift where a new Bound constant was added but OrcMetrics was not updated; custom code invoking the private path via reflection or a patched fork passing an unexpected bound.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- Failed to get statistics from writer
- Failed to open file
- Batch reading is not supported in non-vectorized reader
- Can't create file
- Can't get Stripe's length from the file writer with path
AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12).
Data as JSON: /api/errors/9eceeff9ab3ae137.
Report an issue: GitHub.
Appendix: source
Thrown at orc/src/main/java/org/apache/iceberg/orc/OrcMetrics.java:372
|| type.typeId() != Type.TypeID.STRING
|| value == null) {
return value;
}
CharSequence charSequence = (CharSequence) value;
MetricsModes.Truncate truncateMode = (MetricsModes.Truncate) metricsMode;
int truncateLength = truncateMode.length();
switch (bound) {
case UPPER:
return Optional.ofNullable(
UnicodeUtil.truncateStringMax(Literal.of(charSequence), truncateLength))
.map(Literal::value)
.orElse(charSequence);
case LOWER:
return UnicodeUtil.truncateStringMin(Literal.of(charSequence), truncateLength).value();
default:
throw new RuntimeException("No other bound is defined.");
}
}
private static Set<Integer> statsColumns(TypeDescription schema) {
return OrcSchemaVisitor.visit(schema, new StatsColumnsVisitor());
}
private static class StatsColumnsVisitor extends OrcSchemaVisitor<Set<Integer>> {
@Override
public Set<Integer> record(
TypeDescription record, List<String> names, List<Set<Integer>> fields) {
ImmutableSet.Builder<Integer> result = ImmutableSet.builder();
fields.stream().filter(Objects::nonNull).forEach(result::addAll);
record.getChildren().stream()
.map(ORCSchemaUtil::icebergID)
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(result::add);View on GitHub (pinned to 86d9c8fc54)