apache/beam · error · IllegalArgumentException

Cannot set both endVersion and endTimestamp.

Error message

Cannot set both endVersion and endTimestamp.

What it means

expand() also enforces mutual exclusivity on the end bound: endVersion and endTimestamp cannot both be set. Each defines the end snapshot differently and combining them is ambiguous, so it throws IllegalArgumentException before reading.

Source

Thrown at sdks/java/io/delta/src/main/java/org/apache/beam/sdk/io/delta/DeltaIO.java:334

      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;
      Long endVersionVal = getEndVersion();
      String endTimestampVal = getEndTimestamp();
      if (endVersionVal != null) {

View on GitHub (pinned to 12126d8942)

Solutions

  1. Remove .withEndTimestamp(...) and keep .withEndVersion(v)
  2. Or remove .withEndVersion(...) and keep .withEndTimestamp(t)
  3. Sanitize pipeline options so only one end-bound field is ever populated

Example fix

// before
DeltaIO.read().withTable(path).withStartVersion(0L).withEndVersion(10L).withEndTimestamp("2024-06-01");
// after
DeltaIO.read().withTable(path).withStartVersion(0L).withEndVersion(10L);
Defensive patterns

Strategy: validation

Validate before calling

if (endVersion != null && endTimestamp != null) {
  throw new IllegalArgumentException("Choose either endVersion or endTimestamp, not both");
}

Prevention

When it happens

Trigger: Calling both .withEndVersion(v) and .withEndTimestamp(t) on the DeltaIO.read() builder.

Common situations: Same merged-config or copy-paste mistakes as the start-bound error; a generic read-range UI exposing both fields; accidental chaining while experimenting with bounded vs unbounded reads.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/056acc1cf5091208. Report an issue: GitHub.