apache/beam · error · IllegalArgumentException

Schema can't be empty

Error message

Schema can't be empty

What it means

When a table read is configured (no SQL), getReadOperation() uses the schema's field names as the columns to fetch. It throws IllegalArgumentException("Schema can't be empty") when the schema equals an empty Schema.builder().build(), i.e. no fields were defined — a table read without columns is meaningless for the connector.

Source

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

            return mode == READ_TIMESTAMP
                ? TimestampBound.ofReadTimestamp(Timestamp.parseTimestamp(readTimestamp))
                : TimestampBound.ofMinReadTimestamp(Timestamp.parseTimestamp(readTimestamp));
          default:
            throw new IllegalArgumentException("Unknown timestamp bound mode: " + mode);
        }
      }

      public ReadOperation getReadOperation() {
        if (sql != null && table != null) {
          throw new IllegalStateException(
              "Query and table params are mutually exclusive. Set just one of them.");
        }
        ReadOperation readOperation = ReadOperation.create();
        if (sql != null) {
          return readOperation.withQuery(sql);
        }
        if (Schema.builder().build().equals(schema)) {
          throw new IllegalArgumentException("Schema can't be empty");
        }
        if (table != null) {
          return readOperation.withTable(table).withColumns(schema.getFieldNames());
        }
        throw new IllegalStateException("Can't happen");
      }
    }

    @Override
    @NonNull
    public PTransform<PBegin, PCollection<Row>> buildExternal(
        ReadBuilder.Configuration configuration) {
      configuration.checkMandatoryFields();

      SpannerIO.Read readTransform =
          SpannerIO.read()
              .withProjectId(configuration.projectId)
              .withDatabaseId(configuration.databaseId)

View on GitHub (pinned to 12126d8942)

Solutions

  1. Add .withSchema(Schema.builder().addInt64Field("id").addStringField("name").build()) listing the columns you want.
  2. Alternatively use .withQuery("SELECT * FROM table") if you want all columns without declaring a schema.
  3. Ensure the columns in the schema match actual Spanner table column names (withColumns uses schema.getFieldNames()).

Example fix

// before
SpannerIO.read().withInstanceId("i").withDatabaseId("db").withTable("users")
// after
SpannerIO.read().withTable("users").withSchema(Schema.builder().addInt64Field("id").addStringField("name").build())
Defensive patterns

Strategy: validation

Validate before calling

if (sql == null && (schema == null || schema.getFields().isEmpty())) { throw new IllegalArgumentException("table reads require a non-empty schema listing columns"); }

Try / catch

try { ReadOperation op = config.getReadOperation(); } catch (IllegalArgumentException e) { throw new ConfigException("Spanner table read needs columns: " + e.getMessage(), e); }

Prevention

When it happens

Trigger: Calling SpannerIO.read().withTable("my_table") without .withSchema(...) or with an empty schema, or building the schema from a config/JSON list of columns that was empty.

Common situations: Users assuming the connector will auto-discover all columns when reading a table (it requires an explicit column list via schema); deserialized schemas where fields array was empty; migration from query-based reads where schema was optional.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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