{"record":{"id":"309a259fd8af997d","repo":"quarkusio/quarkus","slug":"restclientproxy-is-closed","errorCode":null,"errorMessage":"RestClientProxy is closed","messagePattern":"RestClientProxy is closed","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"extensions/resteasy-classic/resteasy-client/runtime/src/main/java/io/quarkus/restclient/runtime/QuarkusProxyInvocationHandler.java","lineNumber":105,"sourceCode":"            this.interceptorBindingsMap = Collections.emptyMap();\n        }\n    }\n\n    @Override\n    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n        if (RestClientProxy.class.equals(method.getDeclaringClass())) {\n            return invokeRestClientProxyMethod(method);\n        }\n        // Autocloseable/Closeable\n        if (method.getName().equals(\"close\") && (args == null || args.length == 0)) {\n            close();\n            return null;\n        }\n        // Check if this proxy is closed or the client itself is closed. The client may be closed if this proxy was a\n        // sub-resource and the resource client itself was closed.\n        if (closed.get() || client.isClosed()) {\n            closed.set(true);\n            throw new IllegalStateException(\"RestClientProxy is closed\");\n        }\n\n        boolean replacementNeeded = false;\n        Object[] argsReplacement = args != null ? new Object[args.length] : null;\n        Annotation[][] parameterAnnotations = method.getParameterAnnotations();\n\n        if (args != null) {\n            for (Object p : providerInstances) {\n                if (p instanceof ParamConverterProvider) {\n\n                    int index = 0;\n                    for (Object arg : args) {\n                        // ParamConverter's are not allowed to be passed null values. If we have a null value do not process\n                        // it through the provider.\n                        if (arg == null) {\n                            continue;\n                        }\n","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/resteasy-classic/resteasy-client/runtime/src/main/java/io/quarkus/restclient/runtime/QuarkusProxyInvocationHandler.java#L87-L123","documentation":"Thrown by QuarkusProxyInvocationHandler.invoke when a method is called on a MicroProfile REST client proxy that has already been closed, or when the underlying JAX-RS Client that owns it is closed (e.g. a sub-resource whose parent client was closed). Quarkus marks the proxy closed and refuses further calls rather than operating on a dead client.","triggerScenarios":"Calling any @Path-annotated method on a REST client proxy after calling close() on it; using a sub-resource obtained from a parent client after the parent client/proxy was closed; invoking a proxy after its scope (e.g. @Dependent bean destroy or programmatic client shutdown) ended.","commonSituations":"Caching a REST client proxy in a long-lived object after its CDI scope ended; closing a parent client and then reusing sub-resources; calling close() in a finally block and then retrying the request; lifecycle changes after upgrading Quarkus where client scoping changed.","solutions":["Remove the close() call or obtain a new proxy (e.g. via QuarkusRestClientBuilder or CDI injection) instead of reusing the closed one","Keep the parent client open as long as any sub-resource proxies derived from it are in use","Inject the client with an appropriate CDI scope (e.g. @ApplicationScoped) so it isn't destroyed while still referenced","Check application logic that closes clients in finally blocks when failures/retries may reuse them later"],"exampleFix":"// before\nMyClient c = RestClientBuilder.newBuilder().baseUri(uri).build(MyClient.class);\nResponse r1 = c.get();\nc.close();\nResponse r2 = c.get(); // IllegalStateException: RestClientProxy is closed\n// after\ntry (MyClient c = RestClientBuilder.newBuilder().baseUri(uri).build(MyClient.class)) {\n    Response r1 = c.get();\n    Response r2 = c.get(); // reuse before closing\n}","handlingStrategy":"try-catch","validationCode":"RestClientProxy proxy = (RestClientProxy) client;\nif (proxy != null) {\n    try {\n        JaxRsClientImpl.class.getDeclaredMethod(\"isClosed\");\n    } catch (NoSuchMethodException ignored) {\n    }\n    // Prefer tracking lifecycle yourself:\n    boolean usable = !closedFlag.get();\n}","typeGuard":"static boolean isUsable(MyClient c) { return c instanceof RestClientProxy && !closedProxies.contains(c); }","tryCatchPattern":"try {\n    response = client.get();\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"RestClientProxy is closed\")) {\n        client = buildClient(); // recreate\n        response = client.get();\n    } else {\n        throw e;\n    }\n}","preventionTips":["Only close clients/proxies when you are truly done with them and all derived sub-resources","Prefer CDI-managed injection over manual lifecycle management","Never cache proxies beyond the scope that created them","Avoid close() in finally blocks if a retry path may reuse the proxy"],"tags":["rest-client","lifecycle","illegal-state","quarkus"],"backgroundTag":"rest-client-already-closed","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}