{"record":{"id":"210155dfb1427ab6","repo":"apache/beam","slug":"both-clientcertpath-and-clientcertkeypath-must-be-specified","errorCode":null,"errorMessage":"Both clientCertPath and clientCertKeyPath must be specified together.","messagePattern":"Both clientCertPath and clientCertKeyPath must be specified together\\.","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerTransformRegistrar.java","lineNumber":134,"sourceCode":"      this.clientCertPath = clientCertPath;\n    }\n\n    public void setClientCertKeyPath(@Nullable String clientCertKeyPath) {\n      this.clientCertKeyPath = clientCertKeyPath;\n    }\n\n    void checkMandatoryFields() {\n      if (projectId.isEmpty()) {\n        throw new IllegalArgumentException(\"projectId can't be empty\");\n      }\n      if (databaseId.isEmpty()) {\n        throw new IllegalArgumentException(\"databaseId can't be empty\");\n      }\n      if (instanceId.isEmpty()) {\n        throw new IllegalArgumentException(\"instanceId can't be empty\");\n      }\n      if ((clientCertPath != null) != (clientCertKeyPath != null)) {\n        throw new IllegalArgumentException(\n            \"Both clientCertPath and clientCertKeyPath must be specified together.\");\n      }\n    }\n  }\n\n  public static class ReadBuilder\n      implements ExternalTransformBuilder<ReadBuilder.Configuration, PBegin, PCollection<Row>> {\n\n    public static class Configuration extends CrossLanguageConfiguration {\n      // TODO: https://github.com/apache/beam/issues/20415 Come up with something to determine\n      // schema without this explicit parameter\n      private Schema schema = Schema.builder().build();\n      private @Nullable String sql;\n      private @Nullable String table;\n      private @Nullable Boolean batching;\n      private @Nullable String timestampBoundMode;\n      private @Nullable String readTimestamp;\n      private @Nullable String timeUnit;","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/io/google-cloud-platform/src/main/java/org/apache/beam/sdk/io/gcp/spanner/SpannerTransformRegistrar.java#L116-L152","documentation":"When configuring client-side TLS certificates for Spanner (e.g. for a emulator or proxy), the cert path and the cert key path must be supplied as a pair. checkMandatoryFields() throws this IllegalArgumentException when exactly one of clientCertPath/clientCertKeyPath is non-null, since a one-sided configuration can never produce a usable key store.","triggerScenarios":"Calling withClientCertPath(path) without withClientCertKeyPath(keyPath), or vice versa, on the Spanner builder/configuration; one of the two values null because a config file only defined one entry.","commonSituations":"Setting up Spanner against an emulator/mtls proxy where docs mention both values but only the cert was wired in; templated configs where the key path secret was not mounted; refactoring that dropped one of the two setters.","solutions":["Always call both .withClientCertPath(certPath) and .withClientCertKeyPath(keyPath) with non-null values.","If you don't need mTLS, remove both setters so neither is set.","Check your config/secret store contains both the certificate and its key."],"exampleFix":"// before\nbuilder.withClientCertPath(\"/certs/client.pem\")\n// after\nbuilder.withClientCertPath(\"/certs/client.pem\").withClientCertKeyPath(\"/certs/client.key\")","handlingStrategy":"validation","validationCode":"if ((clientCertPath == null) != (clientCertKeyPath == null)) { throw new IllegalArgumentException(\"clientCertPath and clientCertKeyPath must be set together\"); }","typeGuard":null,"tryCatchPattern":"try { builder.withClientCertPath(cert).withClientCertKeyPath(key); } catch (IllegalArgumentException e) { throw new ConfigException(\"mTLS config incomplete: \" + e.getMessage(), e); }","preventionTips":["Wrap cert/key into a single config object so they are always set together.","Check secret mounting includes both files before launching the pipeline.","Prefer the default transport (no explicit certs) unless mTLS to a proxy/emulator is required."],"tags":["java","apache-beam","google-cloud-spanner","tls","validation"],"backgroundTag":"conflicting-config-options","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}