quarkusio/quarkus · error · IllegalStateException

Client is closed

Error message

Client is closed

What it means

A REST Client operation was invoked after Client.close(). ClientImpl.abortIfClosed() is called by target(), invocation(), getSslContext(), getHostnameVerifier(), getConfiguration() and property(); once isClosed is set, all of these throw IllegalStateException because the client's resources have been released.

Source

Thrown at independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientImpl.java:274

    public ClientContext getClientContext() {
        return clientContext;
    }

    @Override
    public void close() {
        if (isClosed)
            return;
        isClosed = true;
        httpClient.close();
        if (closeVertx) {
            vertx.close();
        }
        log.debug("Client is closed");
    }

    void abortIfClosed() {
        if (isClosed)
            throw new IllegalStateException("Client is closed");
    }

    public String getUserAgent() {
        return userAgent;
    }

    public String getTlsConfigName() {
        return tlsConfigName;
    }

    @Override
    public WebTarget target(String uri) {
        // close is checked in the other target call
        Objects.requireNonNull(uri);
        return target(UriBuilder.fromUri(uri));
    }

    @Override

View on GitHub (pinned to e1c734241f)

Solutions

  1. Create a new Client via ClientBuilder after close; closed clients cannot be reopened
  2. Keep a single long-lived Client open for the application lifetime instead of closing per request
  3. Guard shared client references: null out or replace the field upon close so no stale reference is used
  4. Do not close injected MicroProfile REST Client proxies yourself; let the container manage their lifecycle

Example fix

// before
Client client = ClientBuilder.newBuilder().build();
client.close();
WebTarget t = client.target("https://api"); // IllegalStateException: Client is closed

// after
Client client = ClientBuilder.newBuilder().build();
WebTarget t = client.target("https://api");
// close only when truly done, and never reuse afterwards
Defensive patterns

Strategy: try-catch

Validate before calling

// track close state yourself when sharing a Client
if (clientField == null || clientClosed) {
    clientField = ClientBuilder.newBuilder().build(); clientClosed = false;
}
clientField.target(url);

Type guard

boolean clientUsable(Client c) {
    try { c.getConfiguration(); return true; }
    catch (IllegalStateException e) { return false; }
}

Try / catch

try {
    return client.target(url).request().get();
} catch (IllegalStateException e) {
    if (e.getMessage().equals("Client is closed")) {
        client = ClientBuilder.newBuilder().build(); // rebuild
        return client.target(url).request().get();
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling client.target(...), client.property(...), client.getConfiguration() or creating an invocation after close() was called on that Client instance (directly, via try-with-resources, or via an injected @CleanupClosed client / RestClient proxy lifecycle end).

Common situations: Closing the client in a finally block then reusing the field later; caching a Client in a singleton and closing it at shutdown while requests still arrive; CDI-managed REST client proxies used after their scope ended; double-close then rebuild attempts.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/70539691ff217b66. Report an issue: GitHub.