{"id":"d37f66b3d76e19ac","repo":"apache/kafka","slug":"this-consumer-has-already-been-closed","errorCode":null,"errorMessage":"This consumer has already been closed.","messagePattern":"This consumer has already been closed\\.","errorType":"exception","errorClass":"java.lang.IllegalStateException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/clients/consumer/internals/AsyncKafkaConsumer.java","lineNumber":2207,"sourceCode":"\n    @Override\n    public void subscribe(Pattern pattern, ConsumerRebalanceListener listener) {\n        if (listener == null)\n            throw new IllegalArgumentException(\"RebalanceListener cannot be null\");\n\n        subscribeInternal(pattern, Optional.of(listener));\n    }\n\n    /**\n     * Acquire the light lock and ensure that the consumer hasn't been closed.\n     *\n     * @throws IllegalStateException If the consumer has been closed\n     */\n    private void acquireAndEnsureOpen() {\n        acquire();\n        if (this.closed) {\n            release();\n            throw new IllegalStateException(\"This consumer has already been closed.\");\n        }\n\n        try {\n            metadata.maybeThrowBootstrapFatalException();\n        } catch (RuntimeException e) {\n            release();\n            throw e;\n        }\n    }\n\n    /**\n     * Acquire the light lock protecting this consumer from multithreaded access. Instead of blocking\n     * when the lock is not available, however, we just throw an exception (since multithreaded usage is not\n     * supported).\n     *\n     * @throws ConcurrentModificationException if another thread already has the lock\n     */\n    private void acquire() {","sourceCodeStart":2189,"sourceCodeEnd":2225,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/AsyncKafkaConsumer.java#L2189-L2225","documentation":"Thrown by acquireAndEnsureOpen() — the gatekeeper invoked by every public state-mutating consumer method — when this.closed is true. Once KafkaConsumer.close() has run, the instance is permanently unusable; any subsequent call (poll, subscribe, assign, commit, etc.) trips this IllegalStateException. The release() is called before throwing so the internal lock is not leaked.","triggerScenarios":"Calling any consumer method after close() returned; calling poll() in a loop after another thread or a shutdown hook closed the consumer; using a consumer inside a try-with-resources block and then accessing it after the block; Spring @PreDestroy closing the consumer before an in-flight task finished.","commonSituations":"Graceful shutdown hooks racing with worker threads; connection pools/wrappers that close consumers on error and then retry on the same instance; test fixtures that close consumers in @AfterEach but a background thread still references them; reactive frameworks cancelling a subscription that triggers close while a poll is queued.","solutions":["Ensure no thread can call the consumer after close(): use a volatile closed flag or an AtomicReference<KafkaConsumer> swapped to null on close.","For thread handoff, publish the consumer via a thread-safe holder and have the consumer thread check isAlive / closed before each poll.","In Spring, scope the consumer bean to the listener container's lifecycle so @PreDestroy and the consumer thread cannot race.","If using try-with-resources, perform all consumer operations inside the block."],"exampleFix":"// before\nKafkaConsumer<String,String> c = new KafkaConsumer<>(props);\n// ... later, c.close() called by shutdown hook\nc.poll(Duration.ofMillis(100)); // throws\n\n// after\nprivate final AtomicReference<KafkaConsumer<String,String>> ref = new AtomicReference<>(c);\n// shutdown hook:\nKafkaConsumer<String,String> c = ref.getAndSet(null);\nif (c != null) c.close();\n// consumer thread:\nKafkaConsumer<String,String> cur = ref.get();\nif (cur == null) return; // already closed\ntry { cur.poll(Duration.ofMillis(100)); }\ncatch (IllegalStateException e) { /* expected during shutdown */ }","handlingStrategy":"try-catch","validationCode":"// Track closed state alongside the consumer in your wrapper:\nprivate volatile boolean closed = false;\npublic synchronized void safeClose() {\n    if (closed) return;\n    closed = true;\n    consumer.close();\n}\n// Before any consumer call:\nif (closed) throw new IllegalStateException(\"consumer closed\");","typeGuard":"// Cannot introspect KafkaConsumer#closed from outside; track locally.\n// Use a wrapper exposing isClosed():\nfinal class SafeConsumer<K,V> {\n    private final KafkaConsumer<K,V> delegate;\n    private volatile boolean closed = false;\n    boolean isClosed() { return closed; }\n}","tryCatchPattern":"try {\n    consumer.poll(Duration.ofMillis(100));\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"already been closed\")) {\n        log.info(\"Consumer closed; skipping further poll\");\n        return; // or recreate the consumer\n    }\n    throw e;\n}","preventionTips":["Own the consumer lifecycle in one place (e.g. a try-with-resources or a managed bean).","After close(), null out the reference and guard all access with a null check or isClosed() flag.","Never share the consumer across components with independent close() calls; centralize shutdown.","If you recreate consumers, use a wrapper that swaps the underlying instance atomically and exposes isClosed()."],"tags":["consumer","lifecycle","closed","illegal-state","shutdown","java"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}