apache/beam · error · IllegalArgumentException
tvfNameList is only supported for change streams with MUTABL
Error message
tvfNameList is only supported for change streams with MUTABLE_KEY_RANGE mode
What it means
When reading change streams, a list of table-valued-function (TVF) names (per-placement TVFs) may only be supplied if the change stream was created with MUTABLE_KEY_RANGE mode. SpannerIO checks isMutableChangeStream against the database and throws IllegalArgumentException if tvfNameList is provided for a non-mutable change stream.
Source
Thrown at sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerIO.java:2314
getInclusiveEndAt().compareTo(MAX_INCLUSIVE_END_AT) > 0
? MAX_INCLUSIVE_END_AT
: getInclusiveEndAt();
final List<String> tvfNameList = getTvfNameList();
final MapperFactory mapperFactory = new MapperFactory(changeStreamDatabaseDialect);
final ChangeStreamMetrics metrics = new ChangeStreamMetrics();
final RpcPriority rpcPriority = MoreObjects.firstNonNull(getRpcPriority(), RpcPriority.HIGH);
final SpannerAccessor spannerAccessor =
SpannerAccessor.getOrCreate(
changeStreamSpannerConfig,
input.getPipeline().getOptions().as(SdkHarnessOptions.class).getOpenTelemetry());
final boolean isMutableChangeStream =
isMutableChangeStream(
spannerAccessor.getDatabaseClient(), changeStreamDatabaseDialect, changeStreamName);
LOG.info("The change stream {} is mutable: {}", changeStreamName, isMutableChangeStream);
List<String> quoteEscapedTvfNameList = null;
if (tvfNameList != null && !tvfNameList.isEmpty()) {
if (!isMutableChangeStream) {
throw new IllegalArgumentException(
"tvfNameList is only supported for change streams with MUTABLE_KEY_RANGE mode");
}
// TODO: if !per_placement_tvf=true, throw exception.
quoteEscapedTvfNameList = new ArrayList<>();
for (String tvfName : tvfNameList) {
quoteEscapedTvfNameList.add(escapeQuotes(tvfName));
}
checkTvfExistence(spannerAccessor.getDatabaseClient(), quoteEscapedTvfNameList);
}
final DaoFactory daoFactory =
new DaoFactory(
changeStreamSpannerConfig,
changeStreamName,
quoteEscapedTvfNameList,
partitionMetadataSpannerConfig,
partitionMetadataTableNames,
rpcPriority,
input.getPipeline().getOptions().getJobName(),View on GitHub (pinned to 12126d8942)
Solutions
- Recreate the change stream with MODE MUTABLE_KEY_RANGE in Spanner, or
- Remove the tvfNameList option when reading a non-mutable change stream
- Verify the changeStreamName points to the intended change stream
- Check the logged 'is mutable' line in pipeline logs to confirm the detected mode
Example fix
// before (non-mutable stream)
.readChangeStream().withChangeStreamName("cs").withTvfNameList(List.of("cs_tvf"))
// after
CREATE CHANGE STREAM cs MODE MUTABLE_KEY_RANGE; -- then use tvfNameList Defensive patterns
Strategy: validation
Validate before calling
if (tvfNameList != null && !tvfNameList.isEmpty() && !changeStreamIsMutable(dbClient, changeStreamName)) {
throw new IllegalArgumentException("tvfNameList requires MUTABLE_KEY_RANGE change stream");
} Try / catch
try { pipeline.apply(readChangeStream); } catch (IllegalArgumentException e) { /* fall back to non-TVF read or recreate stream */ } Prevention
- Create change streams with MODE MUTABLE_KEY_RANGE when using per-placement TVFs
- Keep change stream definitions in migration scripts reviewed alongside pipeline config
- Verify change stream mode before enabling tvfNameList
When it happens
Trigger: Calling .withTvfNameList(...) (or equivalent option) on SpannerIO.readChangeStream() while the target change stream's mode is not MUTABLE_KEY_RANGE.
Common situations: Reusing pipeline code written for mutable change streams against an older/standard change stream; migrating configs without recreating the change stream with MODE MUTABLE_KEY_RANGE; typos pointing at the wrong change stream name.
Related errors
- Start time cannot be after end time.
- MetadataInstance can't be empty
- MetadataDatabase can't be empty
- Unknown type {}
- Unknown key part {}
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/f3c196f0180f149b.
Report an issue: GitHub.