apache/pulsar · error · IllegalArgumentException
Both externalExecutorProvider and internalExecutorProvider m
Error message
Both externalExecutorProvider and internalExecutorProvider must be specified or unspecified.
What it means
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.
Source
Thrown at pulsar-client/src/main/java/org/apache/pulsar/client/impl/PulsarClientImpl.java:272
@Builder(builderClassName = "PulsarClientImplBuilder")
PulsarClientImpl(ClientConfigurationData conf, EventLoopGroup eventLoopGroup, ConnectionPool connectionPool,
Timer timer, ExecutorProvider externalExecutorProvider,
ExecutorProvider internalExecutorProvider,
ScheduledExecutorProvider scheduledExecutorProvider,
ExecutorProvider lookupExecutorProvider,
DnsResolverGroupImpl dnsResolverGroup,
MemoryLimitController memoryLimitController) throws PulsarClientException {
EventLoopGroup eventLoopGroupReference = null;
ConnectionPool connectionPoolReference = null;
try {
if (conf == null || isBlank(conf.getServiceUrl())) {
throw new PulsarClientException.InvalidConfigurationException("Invalid client configuration");
}
this.conf = conf;
this.createdEventLoopGroup = eventLoopGroup == null;
this.createdCnxPool = connectionPool == null;
if ((externalExecutorProvider == null) != (internalExecutorProvider == null)) {
throw new IllegalArgumentException(
"Both externalExecutorProvider and internalExecutorProvider must be specified or unspecified.");
}
this.createdExecutorProviders = externalExecutorProvider == null;
this.createdScheduledProviders = scheduledExecutorProvider == null;
this.createdLookupProviders = lookupExecutorProvider == null;
eventLoopGroupReference = eventLoopGroup != null ? eventLoopGroup :
PulsarClientResourcesConfigurer.createEventLoopGroup(conf);
this.eventLoopGroup = eventLoopGroupReference;
this.instrumentProvider = new InstrumentProvider(conf.getOpenTelemetry());
clientClock = conf.getClock();
this.scheduledExecutorProvider = scheduledExecutorProvider != null ? scheduledExecutorProvider :
PulsarClientResourcesConfigurer.createScheduledExecutorProvider(conf);
// PIP-478: resolve the client-side TLS SPI factory (new path) before the connection
// pool and HTTP lookup are created — both read conf.getTlsFactory() to branch onto it.
setupClientTlsFactory();
if (connectionPool != null) {
connectionPoolReference = connectionPool;
dnsResolverGroupLocalInstance = null;View on GitHub (pinned to 820761864e)
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.
Example fix
// before new PulsarClientImpl(conf, eventLoopGroup, externalExecutorProvider, null, ...); // after ExecutorProvider internal = new ExecutorProvider(numThreads, externalExecutorProvider.getScheduledExecutorProvider()); new PulsarClientImpl(conf, eventLoopGroup, externalExecutorProvider, internal, ...);
Defensive patterns
Strategy: validation
Validate before calling
if ((externalExecutorProvider == null) != (internalExecutorProvider == null)) {
throw new IllegalArgumentException("Pass both executor providers or neither");
} Try / catch
try {
client = new PulsarClientImpl(conf, eventLoopGroup, ext, internal, ...);
} catch (IllegalArgumentException e) {
// construct providers as a pair and retry once
internal = new ExecutorProvider(numThreads, ext);
client = new PulsarClientImpl(conf, eventLoopGroup, ext, internal, ...);
} Prevention
- 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.
When it happens
Trigger: Calling the package-private/extended PulsarClientImpl constructor passing an externalExecutorProvider without an internalExecutorProvider (or vice versa).
Common situations: 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.
Related errors
- ResourceGroupCreate: Invalid null ResourceGroup config
- ResourceGroupCreate: can't create resource group with an emp
- Invalid key-shared mode: ${keySharedMode}
- Invalid auto split/merge configuration: ${message}
- Must have at least 1 segment
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/8c17992c6be6508e.
Report an issue: GitHub.