apache/beam · error · java.lang.UnsupportedOperationException
Does not support BeamIOSinkRel in toRowList.
Error message
Does not support BeamIOSinkRel in toRowList.
What it means
BeamEnumerableConverter.toRowList executes a Beam SQL plan and materializes results into an in-memory List<Row>. Sink rel nodes (BeamIOSinkRel, e.g. INSERT/CTAS-style statements that write to a table) do not produce enumerable rows, so calling toRowList on such a plan throws UnsupportedOperationException. The caller should instead use a path that executes the pipeline (such as toEnumerable/BeamSqlEnv execution) rather than collecting rows.
Solutions
- Use toEnumerable (or the sink-execution path) for BeamIOSinkRel plans instead of toRowList
- Restrict toRowList usage to pure SELECT queries; detect DML beforehand (e.g. check the parsed statement type) and route write statements to pipeline execution
- If you only need the side effect of writing, run the pipeline via PipelineResult waitUntilFinish after building from the sink rel, and ignore/empty row results
- Check node instanceof BeamIOSinkRel in your own wrapper before calling toRowList and fail fast with a clearer message
Example fix
// before
List<Row> rows = BeamEnumerableConverter.toRowList(options, relNode); // throws for INSERT
// after
if (relNode instanceof BeamIOSinkRel) {
BeamEnumerableConverter.toEnumerable(options, relNode). enumerator-move/execute pipeline; // execute sink
} else {
List<Row> rows = BeamEnumerableConverter.toRowList(options, relNode);
} Defensive patterns
Strategy: type-guard
Validate before calling
if (relNode instanceof BeamIOSinkRel) {
throw new IllegalArgumentException("Use pipeline/sink execution, not toRowList, for DML statements");
} Type guard
boolean isSinkQuery(BeamRelNode node) { return node instanceof BeamIOSinkRel; } Try / catch
try {
rows = BeamEnumerableConverter.toRowList(options, node);
} catch (UnsupportedOperationException e) {
if (e.getMessage().contains("BeamIOSinkRel")) {
BeamEnumerableConverter.toEnumerable(options, node); // execute sink path
} else { throw e; }
} Prevention
- Only call toRowList for SELECT-style plans; route INSERT/write statements to toEnumerable or pipeline execution
- Check the parsed SQL statement type (DML vs query) before choosing an execution path
- Inspect the plan tree for BeamIOSinkRel before materializing rows
- Keep a wrapper API that picks the right execution mode automatically
When it happens
Trigger: Calling BeamEnumerableConverter.toRowList(options, node) (directly or via the JDBC/Shell default row-collection path) with a parsed and validated BeamRelNode that is a BeamIOSinkRel — i.e. the SQL statement is a DML sink (e.g. INSERT INTO) rather than a SELECT.
Common situations: Executing 'INSERT INTO ... SELECT ...' style statements through a code path that expects to collect rows in memory; using BeamSqlCli or the JDBC adapter configured to return row lists against a write statement; programmatically running sqlEnv.parseQuery(...).compile... and choosing toRowList for a non-SELECT statement.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Does not support queries with LIMIT in toRowList.
- A 'datagen' table requires either 'rows-per-second' (for…
- Adding required columns is not yet supported. Encountered…
- ALTER is not supported for table
- Analytics Function [ ] is not supported
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/fb0d51809dea8734.
Report an issue: GitHub.
Appendix: source
Thrown at sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/impl/rel/BeamEnumerableConverter.java:160
Thread.currentThread().setContextClassLoader(originalClassLoader);
}
}
public static PipelineOptions createPipelineOptions(Map<String, String> map) {
final String[] args = new String[map.size()];
int i = 0;
for (Map.Entry<String, String> entry : map.entrySet()) {
args[i++] = "--" + entry.getKey() + "=" + entry.getValue();
}
PipelineOptions options = PipelineOptionsFactory.fromArgs(args).withValidation().create();
FileSystems.setDefaultPipelineOptions(options);
options.as(ApplicationNameOptions.class).setAppName("BeamSql");
return options;
}
static List<Row> toRowList(PipelineOptions options, BeamRelNode node) {
if (node instanceof BeamIOSinkRel) {
throw new UnsupportedOperationException("Does not support BeamIOSinkRel in toRowList.");
} else if (isLimitQuery(node)) {
throw new UnsupportedOperationException("Does not support queries with LIMIT in toRowList.");
}
return collectRows(options, node).stream().collect(Collectors.toList());
}
static Enumerable<Object> toEnumerable(PipelineOptions options, BeamRelNode node) {
if (node instanceof BeamIOSinkRel) {
return count(options, node);
} else if (isLimitQuery(node)) {
return limitCollect(options, node);
}
return Linq4j.asEnumerable(rowToAvaticaAndUnboxValues(collectRows(options, node)));
}
private static PipelineResult limitRun(
PipelineOptions options,
BeamRelNode node,View on GitHub (pinned to 12126d8942)