apache/beam · error · UnsupportedOperationException

Could not set a field in the BeamRowWrapper because rowData

Error message

Could not set a field in the BeamRowWrapper because rowData is read-only

What it means

BeamRowWrapper implements Iceberg's StructLike for Beam Row objects, which are immutable. Its set(int,T) always throws UnsupportedOperationException because mutating a Beam Row is not possible; the wrapper is read-only by design and intended for reading field values only.

Source

Thrown at sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/BeamRowWrapper.java:106

  /**
   * Retrieves a field value from the wrapped row, performing any necessary type conversion to match
   * Iceberg's internal expectations (e.g., converting Timestamps to microseconds).
   */
  @Override
  public <T> @Nullable T get(int pos, Class<T> javaClass) {
    if (row == null || row.getValue(pos) == null) {
      return null;
    } else if (getters[pos] != null) {
      return javaClass.cast(getters[pos].get(checkStateNotNull(row), pos));
    }

    return javaClass.cast(checkStateNotNull(row).getValue(pos));
  }

  @Override
  public <T> void set(int pos, T value) {
    throw new UnsupportedOperationException(
        "Could not set a field in the BeamRowWrapper because rowData is read-only");
  }

  private interface PositionalGetter<T> {
    T get(Row data, int pos);
  }

  /**
   * Factory method to create a getter that handles type-specific conversion logic.
   *
   * <p>Handles special cases:
   *
   * <ul>
   *   <li>UUID: Converts {@code byte[]} to Iceberg's UUID representation.
   *   <li>DateTime: Converts Beam {@code DateTime} or logical types to microsecond timestamps.
   *   <li>Nested Rows: Recursively wraps nested structures in a new {@code BeamRowWrapper}.
   * </ul>
   */

View on GitHub (pinned to 12126d8942)

Solutions

  1. Build a new Beam Row with Row.withSchema(...).addValues(...).build() instead of mutating the existing one
  2. Use a mutable StructLike implementation (e.g. Iceberg's InternalRecordWrapper or a GenericData.Record) for write paths
  3. Copy values out of the wrapper into a builder before modification
  4. Ensure only read accessors (get) are used with BeamRowWrapper

Example fix

// before
wrapper.set(pos, newValue); // throws
// after
Row updated = Row.withSchema(schema).addValues(newValues).build();
Defensive patterns

Strategy: validation

Validate before calling

if (structLike instanceof BeamRowWrapper) throw new IllegalStateException("BeamRowWrapper is read-only; use Row builder");

Type guard

null

Try / catch

try { wrapper.set(pos, v); } catch (UnsupportedOperationException e) { row = rebuildRow(row, pos, v); }

Prevention

When it happens

Trigger: Any code path (e.g. Iceberg writer internals, generic updaters, or schema evolution helpers) that calls StructLike.set on a row wrapped by BeamRowWrapper; using the wrapper as a mutable record buffer.

Common situations: Bridging Iceberg APIs that expect a writable StructLike; copying logic from GenericRecord wrappers that allow set(); passing BeamRowWrapper to writers that mutate in place.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/509c307561f1d565. Report an issue: GitHub.