{"record":{"id":"8c17992c6be6508e","repo":"apache/pulsar","slug":"both-externalexecutorprovider-and-internalexecutor","errorCode":null,"errorMessage":"Both externalExecutorProvider and internalExecutorProvider must be specified or unspecified.","messagePattern":"Both externalExecutorProvider and internalExecutorProvider must be specified or unspecified\\.","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-client/src/main/java/org/apache/pulsar/client/impl/PulsarClientImpl.java","lineNumber":272,"sourceCode":"    @Builder(builderClassName = \"PulsarClientImplBuilder\")\n    PulsarClientImpl(ClientConfigurationData conf, EventLoopGroup eventLoopGroup, ConnectionPool connectionPool,\n                     Timer timer, ExecutorProvider externalExecutorProvider,\n                     ExecutorProvider internalExecutorProvider,\n                     ScheduledExecutorProvider scheduledExecutorProvider,\n                     ExecutorProvider lookupExecutorProvider,\n                     DnsResolverGroupImpl dnsResolverGroup,\n                     MemoryLimitController memoryLimitController) throws PulsarClientException {\n        EventLoopGroup eventLoopGroupReference = null;\n        ConnectionPool connectionPoolReference = null;\n        try {\n            if (conf == null || isBlank(conf.getServiceUrl())) {\n                throw new PulsarClientException.InvalidConfigurationException(\"Invalid client configuration\");\n            }\n            this.conf = conf;\n            this.createdEventLoopGroup = eventLoopGroup == null;\n            this.createdCnxPool = connectionPool == null;\n            if ((externalExecutorProvider == null) != (internalExecutorProvider == null)) {\n                throw new IllegalArgumentException(\n                        \"Both externalExecutorProvider and internalExecutorProvider must be specified or unspecified.\");\n            }\n            this.createdExecutorProviders = externalExecutorProvider == null;\n            this.createdScheduledProviders = scheduledExecutorProvider == null;\n            this.createdLookupProviders = lookupExecutorProvider == null;\n            eventLoopGroupReference = eventLoopGroup != null ? eventLoopGroup :\n                    PulsarClientResourcesConfigurer.createEventLoopGroup(conf);\n            this.eventLoopGroup = eventLoopGroupReference;\n            this.instrumentProvider = new InstrumentProvider(conf.getOpenTelemetry());\n            clientClock = conf.getClock();\n            this.scheduledExecutorProvider = scheduledExecutorProvider != null ? scheduledExecutorProvider :\n                    PulsarClientResourcesConfigurer.createScheduledExecutorProvider(conf);\n            // PIP-478: resolve the client-side TLS SPI factory (new path) before the connection\n            // pool and HTTP lookup are created — both read conf.getTlsFactory() to branch onto it.\n            setupClientTlsFactory();\n            if (connectionPool != null) {\n                connectionPoolReference = connectionPool;\n                dnsResolverGroupLocalInstance = null;","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-client/src/main/java/org/apache/pulsar/client/impl/PulsarClientImpl.java#L254-L290","documentation":"The internal PulsarClientImpl constructor accepts paired executor providers: externalExecutorProvider (client API surface) and internalExecutorProvider (internal bookkeeping). If exactly one of them is null the wiring is incomplete, so the constructor throws IllegalArgumentException to prevent a half-initialized client.","triggerScenarios":"Calling the package-private/extended PulsarClientImpl constructor passing an externalExecutorProvider without an internalExecutorProvider (or vice versa).","commonSituations":"Embedded/advanced integrations that build the client with custom executors (e.g. tests, framework adapters) updating one argument but not the other after a signature change.","solutions":["Pass both externalExecutorProvider and internalExecutorProvider, or pass null for both so the client creates its own providers.","Update the calling code to construct the two providers together (typically the internal one derives from the external one).","Prefer the public PulsarClient.builder() API, which never requires these arguments."],"exampleFix":"// before\nnew PulsarClientImpl(conf, eventLoopGroup, externalExecutorProvider, null, ...);\n// after\nExecutorProvider internal = new ExecutorProvider(numThreads, externalExecutorProvider.getScheduledExecutorProvider());\nnew PulsarClientImpl(conf, eventLoopGroup, externalExecutorProvider, internal, ...);","handlingStrategy":"validation","validationCode":"if ((externalExecutorProvider == null) != (internalExecutorProvider == null)) {\n    throw new IllegalArgumentException(\"Pass both executor providers or neither\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    client = new PulsarClientImpl(conf, eventLoopGroup, ext, internal, ...);\n} catch (IllegalArgumentException e) {\n    // construct providers as a pair and retry once\n    internal = new ExecutorProvider(numThreads, ext);\n    client = new PulsarClientImpl(conf, eventLoopGroup, ext, internal, ...);\n}","preventionTips":["Always create external and internal executor providers together in one factory method.","Prefer the public builder API to avoid internal constructor pitfalls.","Update wrapper/adaptor code when PulsarClientImpl constructor signatures change."],"tags":["pulsar","client-configuration","executor","illegal-argument"],"backgroundTag":"invalid-constructor-arguments","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}