apache/beam · error · IllegalArgumentException

Both query and table cannot be specified at the same time…

Error message

Both query and table cannot be specified at the same time for SpannerIO.read().

What it means

SpannerIO.Read's validate/expand logic allows exactly one read source: a SQL query or a table. If a ReadOperation has both query and table set, the transform cannot decide which to use and throws IllegalArgumentException at pipeline construction time (in Read's public validation path).

Solutions

  1. Remove .withQuery(...) if reading a table, or remove .withTable(...) if reading a query
  2. Inspect the ReadOperation/query+table fields to see which is being set twice
  3. If building from config, ensure only one of query/table keys is non-null

Example fix

// before
SpannerIO.read().withQuery("SELECT * FROM t").withTable("t")
// after
SpannerIO.read().withQuery("SELECT * FROM t")
Defensive patterns

Strategy: validation

Validate before calling

boolean hasQuery = readOperation.getQuery() != null;
boolean hasTable = readOperation.getTable() != null;
if (hasQuery && hasTable) { throw new IllegalArgumentException("Set only one of query or table"); }

Try / catch

try { pipeline.apply(SpannerIO.read()...); } catch (IllegalArgumentException e) { LOG.error("SpannerIO config error: {}", e.getMessage()); }

Prevention

When it happens

Trigger: Building SpannerIO.read() with both .withQuery(...) and .withTable(...) on the same Read/ReadOperation, then running the pipeline.

Common situations: Programmatically composing a SpannerIO.read where defaults set a table and user code also sets a query; copy-pasted builder chains; config-driven construction where both keys are populated.

Related errors


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

Appendix: source

Thrown at sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerIO.java:1116

      if (getReadOperation().getQuery() != null) {
        return SpannerQuerySourceDef.create(getSpannerConfig(), getReadOperation().getQuery());
      }
      return SpannerTableSourceDef.create(
          getSpannerConfig(), getReadOperation().getTable(), getReadOperation().getColumns());
    }

    @Override
    public PCollection<Struct> expand(PBegin input) {
      getSpannerConfig().validate();
      checkArgument(
          getTimestampBound() != null,
          "SpannerIO.read() runs in a read only transaction and requires timestamp to be set "
              + "with withTimestampBound or withTimestamp method");

      if (getReadOperation().getQuery() != null) {
        // TODO: validate query?
        if (getReadOperation().getTable() != null) {
          throw new IllegalArgumentException(
              "Both query and table cannot be specified at the same time for SpannerIO.read().");
        }
      } else if (getReadOperation().getTable() != null) {
        // Assume read
        checkNotNull(
            getReadOperation().getColumns(),
            "For a read operation SpannerIO.read() requires a list of "
                + "columns to set with withColumns method");
        checkArgument(
            !getReadOperation().getColumns().isEmpty(),
            "For a read operation SpannerIO.read() requires a non-empty"
                + " list of columns to set with withColumns method");
      } else {
        throw new IllegalArgumentException(
            "SpannerIO.read() requires query OR table to set with withTable OR withQuery method.");
      }

      final SpannerSourceDef sourceDef = createSourceDef();

View on GitHub (pinned to 12126d8942)