apache/beam · error · IllegalStateException

private key cannot be null

Error message

private key cannot be null

What it means

When BigtableIO credentials are configured with a service account JSON key file, the code loads the key file into a PKCS12 KeyStore and extracts the 'privatekey' entry. If keyStore.getKey(...) returns null, the key file did not contain the expected private key alias, so an IllegalStateException('private key cannot be null') is thrown. This indicates the supplied file is not a valid Google service account PKCS12 key file.

Solutions

  1. Ensure the key file is a .p12 (PKCS12) Google service account key, not a JSON key file; download the .p12 key from Cloud Console if needed
  2. Prefer Application Default Credentials or UserSuppliedCredentialOptions instead of a raw key file
  3. Verify the key file is not corrupted (open it with `keytool -list -keystore file.p12 -storepass notasecret` and confirm the 'privatekey' alias)
  4. Re-create the service account key if the file predates key format changes or was hand-modified

Example fix

// before
.withCredentialOptions(BeamBigtableIO.CredentialOptions.serviceAccountCredentialOptions("sa.json", null))
// after
.withCredentialOptions(BeamBigtableIO.CredentialOptions.serviceAccountCredentialOptions("sa.p12", serviceAccount))
Defensive patterns

Strategy: validation

Validate before calling

File f = new File(keyPath);
try (FileInputStream fin = new FileInputStream(f)) {
  KeyStore ks = KeyStore.getInstance("PKCS12");
  ks.load(fin, "notasecret".toCharArray());
  if (ks.getKey("privatekey", "notasecret".toCharArray()) == null) {
    throw new IllegalArgumentException("Not a valid service account .p12: 'privatekey' alias missing");
  }
}

Prevention

When it happens

Trigger: CredentialOptions.ServiceModelCredentialOptions with a key file path pointing to a file that is not a valid PKCS12 service account key, or a key file whose 'privatekey' alias entry is absent/renamed; also occurs if the keystore password differs from Google's fixed 'notasecret'.

Common situations: Passing a JSON-format service account key file where the translator expects a .p12 file; passing a non-credential file path; using a key file exported/converted in a way that drops the 'privatekey' alias; stale key files from old project setups.

Related errors


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

Appendix: source

Thrown at sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/bigtable/BigtableConfigTranslator.java:423

        switch (credOptions.getCredentialType()) {
          case DefaultCredentials:
            // Veneer uses default credentials, so no need to reset here
            break;
          case P12:
            String keyFile = ((CredentialOptions.P12CredentialOptions) credOptions).getKeyFile();
            String serviceAccount =
                ((CredentialOptions.P12CredentialOptions) credOptions).getServiceAccount();
            try {
              KeyStore keyStore = KeyStore.getInstance("PKCS12");

              try (FileInputStream fin = new FileInputStream(keyFile)) {
                keyStore.load(fin, "notasecret".toCharArray());
              }
              PrivateKey privateKey =
                  (PrivateKey) keyStore.getKey("privatekey", "notasecret".toCharArray());

              if (privateKey == null) {
                throw new IllegalStateException("private key cannot be null");
              }
              Credentials credentials =
                  ServiceAccountJwtAccessCredentials.newBuilder()
                      .setClientEmail(serviceAccount)
                      .setPrivateKey(privateKey)
                      .build();
              builder.setCredentialFactory(FixedCredentialFactory.create(credentials));
            } catch (GeneralSecurityException exception) {
              throw new RuntimeException("exception while retrieving credentials", exception);
            }
            break;
          case SuppliedCredentials:
            Credentials credentials =
                ((CredentialOptions.UserSuppliedCredentialOptions) credOptions).getCredential();
            builder.setCredentialFactory(FixedCredentialFactory.create(credentials));
            break;
          case SuppliedJson:
            CredentialOptions.JsonCredentialsOptions jsonCredentialsOptions =

View on GitHub (pinned to 12126d8942)