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));
}
@OverrideView on GitHub (pinned to e1c734241f)
Solutions
- Create a new Client via ClientBuilder after close; closed clients cannot be reopened
- Keep a single long-lived Client open for the application lifetime instead of closing per request
- Guard shared client references: null out or replace the field upon close so no stale reference is used
- 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
- Keep one long-lived Client per application; do not close it per request
- Never reuse a Client after close(); drop the reference immediately
- Let the container manage injected MicroProfile REST Client proxies — never call close() on them
- Add a shutdown hook ordering check so the client isn't closed while requests are in flight
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
- OidcClient %s is closed
- RestClientProxy is closed
- The Reactive REST Client needs to be built within the contex
- Stream is closed
- Entity stream has already been read and is not buffered: cal
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/70539691ff217b66.
Report an issue: GitHub.