apache/beam · error · IllegalArgumentException
Cannot set both startVersion and startTimestamp.
Error message
Cannot set both startVersion and startTimestamp.
What it means
expand() enforces that the read start point is unambiguous: startVersion and startTimestamp are mutually exclusive. Both express different semantics (exact snapshot version vs. time travel by timestamp), so setting both is rejected with IllegalArgumentException.
Source
Thrown at sdks/java/io/delta/src/main/java/org/apache/beam/sdk/io/delta/DeltaIO.java:331
}
public ReadChanges withConfig(Map<String, String> config) {
return toBuilder().setHadoopConfig(config).build();
}
@Override
public PCollection<Row> expand(PBegin input) {
String path = getTablePath();
if (path == null) {
throw new IllegalArgumentException("Table path must be set.");
}
if (getStartVersion() == null && getStartTimestamp() == null) {
// TODO: for unbounded reads, support using current HEAD or the latest snapshot
// as the default starting point.
throw new IllegalArgumentException("Either startVersion or startTimestamp must be set.");
}
if (getStartVersion() != null && getStartTimestamp() != null) {
throw new IllegalArgumentException("Cannot set both startVersion and startTimestamp.");
}
if (getEndVersion() != null && getEndTimestamp() != null) {
throw new IllegalArgumentException("Cannot set both endVersion and endTimestamp.");
}
Configuration conf = new Configuration();
Map<String, String> hadoopConfig = getHadoopConfig();
if (hadoopConfig != null) {
for (Map.Entry<String, String> entry : hadoopConfig.entrySet()) {
conf.set(entry.getKey(), entry.getValue());
}
}
Engine engine = DefaultEngine.create(conf);
Table table = Table.forPath(engine, path);
TableImpl tableImpl = (TableImpl) table;
long resolvedEndVersion;View on GitHub (pinned to 12126d8942)
Solutions
- Keep only one: remove .withStartTimestamp(...) and rely on startVersion
- Or remove .withStartVersion(...) and keep .withStartTimestamp(...)
- If both come from config, add explicit validation/mutual-exclusion handling in your pipeline options
Example fix
// before
DeltaIO.read().withTable(path).withStartVersion(5L).withStartTimestamp("2024-01-01T00:00:00");
// after
DeltaIO.read().withTable(path).withStartVersion(5L); Defensive patterns
Strategy: validation
Validate before calling
if (startVersion != null && startTimestamp != null) {
throw new IllegalArgumentException("Choose either startVersion or startTimestamp, not both");
} Prevention
- Expose only one start-bound option in your tooling, or enforce exclusivity at config load
- Add a config-load check that fails fast when both fields are populated
- Prefer startVersion for determinism; use timestamps only when time travel is required
When it happens
Trigger: Calling both .withStartVersion(v) and .withStartTimestamp(t) on the same DeltaIO.read() builder before applying the transform.
Common situations: Merging two configurations (one specifying a version, one a timestamp); copy-pasting from examples that use different start styles; a generic time-travel config exposing both fields and the code forwarding both.
Related errors
- Cannot set both endVersion and endTimestamp.
- Either startVersion or startTimestamp must be set.
- Timing number 0b" + timingNumber.toString(2) + " has more th
- No filesystem found for scheme
- Exploded field %s must be an iterable type, got %s.
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/dd11b08404220e0e.
Report an issue: GitHub.