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
- Build a new Beam Row with Row.withSchema(...).addValues(...).build() instead of mutating the existing one
- Use a mutable StructLike implementation (e.g. Iceberg's InternalRecordWrapper or a GenericData.Record) for write paths
- Copy values out of the wrapper into a builder before modification
- 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
- Treat Beam Row as immutable always
- Use Row.withSchema builders for modifications
- Use writable StructLike impls for Iceberg write paths
- Audit bridging code that calls StructLike.set
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
- smallBatchTag and largeBatchTag must not be null
- Unsupported Beam type for Iceberg timestamp with timezone: {
- Received null value for required field '{fieldName}'.
- Unsupported row type: {valueClass}
- Unsupported Beam type: {typeName}
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/509c307561f1d565.
Report an issue: GitHub.