{"record":{"id":"b0ca0486c65fb5ae","repo":"apache/seatunnel","slug":"options-credentials-path-and-emulator-host-can-b0ca04","errorCode":null,"errorMessage":"Options 'credentials_path' and 'emulator_host' cannot be configured together","messagePattern":"Options 'credentials_path' and 'emulator_host' cannot be configured together","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"seatunnel-connectors-v2/connector-google-pubsub/src/main/java/org/apache/seatunnel/connectors/seatunnel/google/pubsub/config/GooglePubSubSourceConfig.java","lineNumber":59,"sourceCode":"\n    public static GooglePubSubSourceConfig from(ReadonlyConfig config) {\n        String projectId = config.get(GooglePubSubSourceOptions.PROJECT_ID);\n        String subscription = config.get(GooglePubSubSourceOptions.SUBSCRIPTION);\n        String credentialsPath = config.get(GooglePubSubSourceOptions.CREDENTIALS_PATH);\n        String emulatorHost = config.get(GooglePubSubSourceOptions.EMULATOR_HOST);\n        MessageFormat format = config.get(GooglePubSubSourceOptions.FORMAT);\n        String fieldDelimiter = config.get(GooglePubSubSourceOptions.FIELD_DELIMITER);\n        Long maxOutstandingMessages =\n                config.get(GooglePubSubSourceOptions.MAX_OUTSTANDING_MESSAGES);\n        Long maxOutstandingBytes = config.get(GooglePubSubSourceOptions.MAX_OUTSTANDING_BYTES);\n        Integer parallelPullCount = config.get(GooglePubSubSourceOptions.PARALLEL_PULL_COUNT);\n\n        requireNonBlank(projectId, GooglePubSubSourceOptions.PROJECT_ID.key());\n        requireNonBlank(subscription, GooglePubSubSourceOptions.SUBSCRIPTION.key());\n        requireNonBlankIfPresent(credentialsPath, GooglePubSubSourceOptions.CREDENTIALS_PATH.key());\n        requireNonBlankIfPresent(emulatorHost, GooglePubSubSourceOptions.EMULATOR_HOST.key());\n        if (credentialsPath != null && emulatorHost != null) {\n            throw new IllegalArgumentException(\n                    \"Options 'credentials_path' and 'emulator_host' cannot be configured together\");\n        }\n        if (format == MessageFormat.TEXT && fieldDelimiter.isEmpty()) {\n            throw new IllegalArgumentException(\"Option 'field_delimiter' cannot be empty\");\n        }\n        requirePositive(\n                maxOutstandingMessages, GooglePubSubSourceOptions.MAX_OUTSTANDING_MESSAGES.key());\n        requirePositive(maxOutstandingBytes, GooglePubSubSourceOptions.MAX_OUTSTANDING_BYTES.key());\n        requirePositive(parallelPullCount, GooglePubSubSourceOptions.PARALLEL_PULL_COUNT.key());\n\n        return GooglePubSubSourceConfig.builder()\n                .projectId(projectId)\n                .subscription(subscription)\n                .credentialsPath(credentialsPath)\n                .emulatorHost(emulatorHost)\n                .format(format)\n                .fieldDelimiter(fieldDelimiter)\n                .maxOutstandingMessages(maxOutstandingMessages)","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-connectors-v2/connector-google-pubsub/src/main/java/org/apache/seatunnel/connectors/seatunnel/google/pubsub/config/GooglePubSubSourceConfig.java#L41-L77","documentation":"GooglePubSubSourceConfig.from() applies the same validation as the sink config: credentials_path (service-account auth) and emulator_host (local emulator) are mutually exclusive, and specifying both throws IllegalArgumentException at source creation. The source variant uses GooglePubSubSourceOptions keys.","triggerScenarios":"A PubSub source config sets both credentials_path and emulator_host to non-null values; from() runs when the source reader is created, before connecting to Pub/Sub.","commonSituations":"Shared base config carrying credentials_path while a dev profile adds emulator_host; inheriting emulator_host from a local-test template when deploying with real service accounts.","solutions":["Keep only credentials_path for real GCP usage and delete emulator_host.","Keep only emulator_host when developing against the Pub/Sub emulator and remove credentials_path.","Make your config templating emit one or the other based on environment."],"exampleFix":"// before\nsource {\n  GooglePubSub {\n    credentials_path = \"/path/sa.json\"\n    emulator_host = \"localhost:8085\"\n  }\n}\n// after\nsource {\n  GooglePubSub {\n    credentials_path = \"/path/sa.json\"\n  }\n}","handlingStrategy":"validation","validationCode":"// before building the source config\nboolean hasCreds = config.getString(\"credentials_path\") != null;\nboolean hasEmu = config.getString(\"emulator_host\") != null;\nif (hasCreds && hasEmu) throw new IllegalArgumentException(\"Set only one of credentials_path / emulator_host\");","typeGuard":null,"tryCatchPattern":"try {\n  GooglePubSubSourceConfig.from(config);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"cannot be configured together\")) {\n    log.error(\"Source config: remove either credentials_path or emulator_host\");\n  } else throw e;\n}","preventionTips":["Separate emulator-based dev configs from production configs entirely.","Use profile-based templates so credentials_path and emulator_host never coexist.","Review inherited/shared config fragments before merging them into a source config."],"tags":["pubsub","source","config","validation"],"backgroundTag":"mutually-exclusive-options","analyzedSha":"cf67b549a7a6c35fa0beb12d83c62892427ea919","analyzedAt":"2026-09-10T21:44:55.265Z","contentChangedAt":"2026-09-10T21:44:55.265Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}