apache/iceberg · error · IllegalStateException

OptionWriter should only expect at most one field metric…

Error message

OptionWriter should only expect at most one field metric from a primitive writer.Current number of fields: %s, primitive writer type: %s

What it means

OptionWriter wraps a primitive writer for optional (nullable) data and merges column metrics from the underlying writer. It assumes the wrapped primitive writer reports at most one field metric. If the wrapped writer exposes more than one field metric (or an unexpected count), an IllegalStateException is thrown naming the field count and writer class.

Solutions

  1. Fix the wrapped writer so it reports exactly one (or zero) FieldMetrics entries
  2. Do not wrap multi-field writers in OptionWriter; handle optionality at a higher level
  3. If the custom writer legitimately produces multiple metrics, bypass or extend OptionWriter's metric aggregation

Example fix

// before: custom writer reports two field metrics inside OptionWriter
return new OptionWriter<>(writer, cardinality()) where writer has 2 fieldMetrics
// after: report a single metric per primitive writer
public List<FieldMetrics<?>> fields() { return ImmutableList.of(singleMetric); }
Defensive patterns

Strategy: validation

Validate before calling

List<FieldMetrics<?>> metrics = primitiveWriter.fields();
Preconditions.checkState(metrics.size() <= 1,
    "Writer %s must report at most one field metric, got %s",
    primitiveWriter.getClass().getSimpleName(), metrics.size());

Try / catch

try {
  optionWriter.addUpdate(...);
} catch (IllegalStateException e) {
  if (e.getMessage().contains("OptionWriter should only expect")) {
    // log writer class + metrics count; disable metric collection for this writer
  } else throw e;
}

Prevention

When it happens

Trigger: Constructing an OptionWriter around a primitive ParquetValueWriter whose fieldWriters()/metrics() return more than one FieldMetrics entry — typically after adding a custom primitive writer that reports multiple metrics or misreporting the number of fields in a composite writer.

Common situations: Custom Parquet writer implementations returning incorrect metrics counts; a bug when combining optional writers with metric-collecting writers (e.g. floating-point min/max collectors); refactors that make a 'primitive' writer internally multi-column.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at parquet/src/main/java/org/apache/iceberg/parquet/ParquetValueWriters.java:489

            writer.metrics().collect(Collectors.toList());

        if (fieldMetricsFromWriter.isEmpty()) {
          // we are not tracking field metrics for this type ourselves
          return Stream.empty();
        } else if (fieldMetricsFromWriter.size() == 1) {
          FieldMetrics<?> metrics = fieldMetricsFromWriter.get(0);
          return Stream.of(
              new FieldMetrics<>(
                  metrics.id(),
                  metrics.valueCount() + nullValueCount,
                  nullValueCount,
                  metrics.nanValueCount(),
                  metrics.lowerBound(),
                  metrics.upperBound(),
                  metrics.originalType(),
                  metrics.avgValueSizeInBytes()));
        } else {
          throw new IllegalStateException(
              String.format(
                  "OptionWriter should only expect at most one field metric from a primitive writer."
                      + "Current number of fields: %s, primitive writer type: %s",
                  fieldMetricsFromWriter.size(), writer.getClass().getSimpleName()));
        }
      }

      // skipping updating null stats for non-primitive types since we don't use them today, to
      // avoid unnecessary work
      return writer.metrics();
    }
  }

  public abstract static class RepeatedWriter<L, E> implements ParquetValueWriter<L> {
    private final int definitionLevel;
    private final int repetitionLevel;
    private final ParquetValueWriter<E> writer;
    private final List<TripleWriter<?>> children;

View on GitHub (pinned to 86d9c8fc54)