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

  1. Ensure getLookup is only called while the client is open — check client.isClosed() first or restructure shutdown ordering.
  2. Create a new PulsarClient instance if lookups are needed after the previous one was closed.
  3. 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

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


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/ea439b84b1fcb855. Report an issue: GitHub.