apache/beam · error · IllegalArgumentException
Start time cannot be after end time.
Error message
Start time cannot be after end time.
What it means
SpannerIO.readChangeStream() validates that the inclusive start time is not after the inclusive end time, comparing their java.sql.Timestamp values. If inclusiveEndAt is set and start is later than end, it throws IllegalArgumentException because Spanner change streams require a monotonically ordered query window.
Source
Thrown at sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerIO.java:2258
getChangeStreamName() != null,
"SpannerIO.readChangeStream() requires the name of the change stream to be set.");
checkArgument(
getInclusiveStartAt() != null,
"SpannerIO.readChangeStream() requires the start time to be set.");
// Inclusive end at is defaulted to ChangeStreamsContants.MAX_INCLUSIVE_END_AT
checkArgument(
getInclusiveEndAt() != null,
"SpannerIO.readChangeStream() requires the end time to be set. If you'd like to process the stream without an end time, you can omit this parameter.");
if (getMetadataInstance() != null) {
checkArgument(
getMetadataDatabase() != null,
"SpannerIO.readChangeStream() requires the metadata database to be set if metadata instance is set.");
}
// Start time must be before end time
if (getInclusiveEndAt() != null
&& getInclusiveStartAt().toSqlTimestamp().after(getInclusiveEndAt().toSqlTimestamp())) {
throw new IllegalArgumentException("Start time cannot be after end time.");
}
final DatabaseId changeStreamDatabaseId =
DatabaseId.of(
getSpannerConfig().getProjectId().get(),
getSpannerConfig().getInstanceId().get(),
getSpannerConfig().getDatabaseId().get());
final String partitionMetadataInstanceId =
MoreObjects.firstNonNull(
getMetadataInstance(), changeStreamDatabaseId.getInstanceId().getInstance());
final String partitionMetadataDatabaseId =
MoreObjects.firstNonNull(getMetadataDatabase(), changeStreamDatabaseId.getDatabase());
final SpannerConfig changeStreamSpannerConfig = buildChangeStreamSpannerConfig();
final SpannerConfig partitionMetadataSpannerConfig =
MetadataSpannerConfigFactory.create(
changeStreamSpannerConfig, partitionMetadataInstanceId, partitionMetadataDatabaseId);
final Dialect changeStreamDatabaseDialect =View on GitHub (pinned to 12126d8942)
Solutions
- Swap or correct start/end so start <= end before building the transform
- Validate ordering in your code before calling changeStreamStartAt/changeStreamEndAt
- Normalize timezones/units so both values are converted via the same toSqlTimestamp path
Example fix
// before
.changeStreamStartAt(laterTime).changeStreamEndAt(earlierTime)
// after
if (start.after(end)) { /* fix or swap */ }
.changeStreamStartAt(earlierTime).changeStreamEndAt(laterTime) Defensive patterns
Strategy: validation
Validate before calling
if (end != null && start.toSqlTimestamp().after(end.toSqlTimestamp())) {
throw new IllegalArgumentException("Start time cannot be after end time.");
} Try / catch
try { pipeline.apply(readChangeStream); } catch (IllegalArgumentException e) { LOG.error("Invalid change stream window: {}", e.getMessage()); } Prevention
- Compute start/end in one UTC-based code path
- Validate ordering at config load time
- Unit-test timestamp conversions across timezones
When it happens
Trigger: Building ReadChangeStream with .changeStreamStartAt(...) later than .changeStreamEndAt(...) (or .changeStreamInclusiveEndAt(...)), e.g. computed timestamps where start is derived from a later checkpoint than end.
Common situations: Clock/timezone conversions producing inverted ranges; passing end from an older run while start is 'now'; mixing Instant and Timestamp conversions incorrectly.
Related errors
- tvfNameList is only supported for change streams with MUTABL
- 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/b0b6e19cf068eb9e.
Report an issue: GitHub.