apache/pulsar · error · IllegalStateException
Pulsar client has been closed, can not build LookupService w
Error message
Pulsar client has been closed, can not build LookupService when calling get lookup with an url
What it means
getLookup(String serviceUrl) lazily builds a LookupService for a URL. If the client has already been closed, building a new lookup service is impossible, so it throws IllegalStateException asking the caller not to build a lookup after close. This surfaces when code holds a client reference past close() and still queries URL-to-lookup mappings.
Source
Thrown at pulsar-client/src/main/java/org/apache/pulsar/client/impl/PulsarClientImpl.java:1783
/**
* Only for test.
*/
@VisibleForTesting
public CompletableFuture<ClientCnx> getConnection(final String topic) {
return getConnection(topic, cnxPool.genRandomKeyToSelectCon()).thenApply(Pair::getLeft);
}
public CompletableFuture<ClientCnx> getConnection(final String topic, final String url) {
TopicName topicName = TopicName.get(topic);
return getLookup(url).getBroker(topicName)
.thenCompose(lookupResult -> getConnection(lookupResult.getLogicalAddress(),
lookupResult.getPhysicalAddress(), cnxPool.genRandomKeyToSelectCon()));
}
public LookupService getLookup(String serviceUrl) {
return urlLookupMap.computeIfAbsent(serviceUrl, url -> {
if (isClosed()) {
throw new IllegalStateException("Pulsar client has been closed, can not build LookupService when"
+ " calling get lookup with an url");
}
try {
return createLookup(serviceUrl);
} catch (PulsarClientException e) {
log.warn().attr("service", url).exceptionMessage(e).log("Failed to update url to lookup service");
throw new IllegalStateException("Failed to update url " + url);
}
});
}
public CompletableFuture<ClientCnx> getConnectionToServiceUrl() {
if (!lookup.isBinaryProtoLookupService()) {
return FutureUtil.failedFuture(new PulsarClientException.InvalidServiceURL(
"Can't get client connection to HTTP service URL", null));
}
InetSocketAddress address = lookup.resolveHost();
return getConnection(address, address, cnxPool.genRandomKeyToSelectCon());View on GitHub (pinned to 820761864e)
Solutions
- Ensure getLookup is only called while the client is open — check client.isClosed() first or restructure shutdown ordering.
- Create a new PulsarClient instance if lookups are needed after the previous one was closed.
- Guard shared client references with lifecycle management so no component uses them post-close.
Example fix
// before
client.close();
LookupService ls = client.getLookup(url); // IllegalStateException
// after
if (!client.isClosed()) {
LookupService ls = client.getLookup(url);
} Defensive patterns
Strategy: validation
Validate before calling
if (client.isClosed()) {
throw new IllegalStateException("Refusing getLookup(): client already closed");
}
LookupService ls = client.getLookup(url); Try / catch
try {
LookupService ls = client.getLookup(url);
} catch (IllegalStateException e) {
if (e.getMessage().contains("has been closed")) {
client = recreateClient(); // rebuild a fresh client for further lookups
ls = client.getLookup(url);
} else throw e;
} Prevention
- Check isClosed() before any use of a shared client reference.
- Order shutdown so components using getLookup() stop before client.close() runs.
- Wrap client access in a lifecycle manager that recreates the client on demand.
When it happens
Trigger: Calling client.getLookup(url) (public method) after client.close() has completed, or from another thread while the client is being closed.
Common situations: Shutdown hooks or cleanup code that touches the client after close; long-lived caches holding a closed PulsarClient; race between a close() in one thread and getLookup() in another.
Related errors
- the PulsarTlsFactory passed to tlsFactory(...) has already b
- Consumer already closed
- Topic was terminated
- ServiceUrlProvider has already been initialized
- The schema is not a KeyValueSchema
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/ea439b84b1fcb855.
Report an issue: GitHub.