apache/beam · error · IllegalArgumentException

projectId can't be empty

Error message

projectId can't be empty

What it means

SpannerTransformRegistrar's Configuration (the cross-language/schematized transform registration for SpannerRead) validates mandatory fields via checkMandatoryFields(); an empty projectId throws IllegalArgumentException("projectId can't be empty"). A Spanner connection is impossible without a project, so this fails fast at configuration time.

Source

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

    public void setExperimentalHost(@Nullable String experimentalHost) {
      this.experimentalHost = experimentalHost;
    }

    public void setPlainText(@Nullable Boolean plainText) {
      this.plainText = plainText;
    }

    public void setClientCertPath(@Nullable String clientCertPath) {
      this.clientCertPath = clientCertPath;
    }

    public void setClientCertKeyPath(@Nullable String clientCertKeyPath) {
      this.clientCertKeyPath = clientCertKeyPath;
    }

    void checkMandatoryFields() {
      if (projectId.isEmpty()) {
        throw new IllegalArgumentException("projectId can't be empty");
      }
      if (databaseId.isEmpty()) {
        throw new IllegalArgumentException("databaseId can't be empty");
      }
      if (instanceId.isEmpty()) {
        throw new IllegalArgumentException("instanceId can't be empty");
      }
      if ((clientCertPath != null) != (clientCertKeyPath != null)) {
        throw new IllegalArgumentException(
            "Both clientCertPath and clientCertKeyPath must be specified together.");
      }
    }
  }

  public static class ReadBuilder
      implements ExternalTransformBuilder<ReadBuilder.Configuration, PBegin, PCollection<Row>> {

    public static class Configuration extends CrossLanguageConfiguration {

View on GitHub (pinned to 12126d8942)

Solutions

  1. Set projectId explicitly: SpannerConfig.create().withProjectId("my-project") or pass the projectId parameter to the transform.
  2. When using pipeline options, wire the GcpOptions.getProject() into the Spanner config instead of relying on defaults.
  3. For cross-language/YAML usage, supply the projectId field in the transform's arguments.
  4. Validate the config object (projectId, instanceId, databaseId all non-empty) before submitting the pipeline.

Example fix

// before
SpannerConfig config = SpannerConfig.create().withInstanceId("my-instance").withDatabaseId("my-db");
// after
SpannerConfig config = SpannerConfig.create()
    .withProjectId("my-gcp-project")
    .withInstanceId("my-instance")
    .withDatabaseId("my-db");
Defensive patterns

Strategy: validation

Validate before calling

Preconditions.checkArgument(
    config.getProjectId() != null && !config.getProjectId().isEmpty(), "projectId must be set");
Preconditions.checkArgument(!config.getInstanceId().isEmpty(), "instanceId must be set");
Preconditions.checkArgument(!config.getDatabaseId().isEmpty(), "databaseId must be set");

Try / catch

try { pipeline.apply(SpannerRead.of(config).withTable(t)); } catch (IllegalArgumentException e) { if (e.getMessage().contains("projectId can't be empty")) { config = config.withProjectId(gcpOptions.getProject()); } else throw e; }

Prevention

When it happens

Trigger: Submitting a Spanner read/write transform (e.g., via cross-language expansion or SpannerTransformRegistrar.Configuration) where the projectId option was not set or was set to an empty string before checkMandatoryFields() runs.

Common situations: Omitting --projectId in pipeline options when constructing the transform from YAML/SQL/cross-language pipelines; env-provided GCP project not propagated into the configuration; typo between option name and expected key.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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