{"record":{"id":"dc4337cc6f17f9fa","repo":"apache/iceberg","slug":"optionwriter-should-only-expect-at-most-one-field","errorCode":null,"errorMessage":"OptionWriter should only expect at most one field metric from a primitive writer.Current number of fields: %s, primitive writer type: %s","messagePattern":"OptionWriter should only expect at most one field metric from a primitive writer\\.Current number of fields: (.+?), primitive writer type: (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"parquet/src/main/java/org/apache/iceberg/parquet/ParquetValueWriters.java","lineNumber":489,"sourceCode":"            writer.metrics().collect(Collectors.toList());\n\n        if (fieldMetricsFromWriter.isEmpty()) {\n          // we are not tracking field metrics for this type ourselves\n          return Stream.empty();\n        } else if (fieldMetricsFromWriter.size() == 1) {\n          FieldMetrics<?> metrics = fieldMetricsFromWriter.get(0);\n          return Stream.of(\n              new FieldMetrics<>(\n                  metrics.id(),\n                  metrics.valueCount() + nullValueCount,\n                  nullValueCount,\n                  metrics.nanValueCount(),\n                  metrics.lowerBound(),\n                  metrics.upperBound(),\n                  metrics.originalType(),\n                  metrics.avgValueSizeInBytes()));\n        } else {\n          throw new IllegalStateException(\n              String.format(\n                  \"OptionWriter should only expect at most one field metric from a primitive writer.\"\n                      + \"Current number of fields: %s, primitive writer type: %s\",\n                  fieldMetricsFromWriter.size(), writer.getClass().getSimpleName()));\n        }\n      }\n\n      // skipping updating null stats for non-primitive types since we don't use them today, to\n      // avoid unnecessary work\n      return writer.metrics();\n    }\n  }\n\n  public abstract static class RepeatedWriter<L, E> implements ParquetValueWriter<L> {\n    private final int definitionLevel;\n    private final int repetitionLevel;\n    private final ParquetValueWriter<E> writer;\n    private final List<TripleWriter<?>> children;","sourceCodeStart":471,"sourceCodeEnd":507,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/parquet/src/main/java/org/apache/iceberg/parquet/ParquetValueWriters.java#L471-L507","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Fix the wrapped writer so it reports exactly one (or zero) FieldMetrics entries","Do not wrap multi-field writers in OptionWriter; handle optionality at a higher level","If the custom writer legitimately produces multiple metrics, bypass or extend OptionWriter's metric aggregation"],"exampleFix":"// before: custom writer reports two field metrics inside OptionWriter\nreturn new OptionWriter<>(writer, cardinality()) where writer has 2 fieldMetrics\n// after: report a single metric per primitive writer\npublic List<FieldMetrics<?>> fields() { return ImmutableList.of(singleMetric); }","handlingStrategy":"validation","validationCode":"List<FieldMetrics<?>> metrics = primitiveWriter.fields();\nPreconditions.checkState(metrics.size() <= 1,\n    \"Writer %s must report at most one field metric, got %s\",\n    primitiveWriter.getClass().getSimpleName(), metrics.size());","typeGuard":null,"tryCatchPattern":"try {\n  optionWriter.addUpdate(...);\n} catch (IllegalStateException e) {\n  if (e.getMessage().contains(\"OptionWriter should only expect\")) {\n    // log writer class + metrics count; disable metric collection for this writer\n  } else throw e;\n}","preventionTips":["Keep custom primitive writers to a single FieldMetrics each","Unit-test metric collection of every new ParquetValueWriter","Never wrap multi-column writers in OptionWriter"],"tags":["parquet","metrics","invariant-violation"],"backgroundTag":"internal-invariant-violation","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}