{"id":"3d38b74a78c9f335","repo":"apache/kafka","slug":"this-consumer-has-already-been-closed-3d38b7","errorCode":null,"errorMessage":"This consumer has already been closed.","messagePattern":"This consumer has already been closed\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/clients/consumer/internals/ShareConsumerImpl.java","lineNumber":1127,"sourceCode":"\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void wakeup() {\n        wakeupTrigger.wakeup();\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        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() {\n        final Thread thread = Thread.currentThread();","sourceCodeStart":1109,"sourceCodeEnd":1145,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/ShareConsumerImpl.java#L1109-L1145","documentation":"Thrown by acquireAndEnsureOpen() (line 1127) when this.closed is true. Almost every public ShareConsumer method funnels through acquireAndEnsureOpen, so once the consumer is closed any further API call (poll, acknowledge, subscribe, commitSync, metrics, etc.) is rejected immediately rather than operating on released resources. This preserves the lifecycle invariant that a closed consumer cannot be reused.","triggerScenarios":"Calling any public method on a KafkaShareConsumer after close() has returned (or after a try-with-resources block has auto-closed it). Also reached when a background poll loop continues one iteration after the owning component closed the consumer, or when a callback fires post-close.","commonSituations":"try-with-resources scoping error where the consumer is closed at the end of the block but a captured lambda/scheduled task still references it; shutdown ordering in Spring/Quarkus where @PreDestroy on the consumer fires before the poll-loop bean is stopped; handing the consumer to a library that retains it beyond its intended lifetime; double-closing in a finally block after an earlier close.","solutions":["Audit the lifecycle: ensure the poll/acknowledge loop is stopped before consumer.close() is invoked.","Do not reuse a closed consumer — create a new KafkaShareConsumer instance instead.","If using try-with-resources, make sure no asynchronous task (scheduler, callback, listener) outlives the try block.","Add an isOpen()/closed check in your own loop, or cancel the loop's executor before closing."],"exampleFix":"// before\ntry (var consumer = new KafkaShareConsumer<>(props)) {\n    scheduler.scheduleAtFixedRate(() -> consumer.poll(Duration.ofMillis(500)), 0, 1, TimeUnit.SECONDS);\n}\n// scheduler fires after close -> exception\n\n// after\nScheduledFuture<?> task = scheduler.scheduleAtFixedRate(pollLoop, 0, 1, TimeUnit.SECONDS);\ntry (var consumer = new KafkaShareConsumer<>(props)) {\n    // ... use consumer ...\n} finally {\n    task.cancel(false);\n}","handlingStrategy":"validation","validationCode":"// Track the consumer lifecycle in a wrapper so callers cannot touch a closed consumer.\nprivate volatile boolean closed = false;\nprivate final org.apache.kafka.clients.consumer.ShareConsumer<K,V> delegate;\n\npublic void safePoll(long ms) {\n    if (closed) throw new IllegalStateException(\"share consumer already closed\");\n    delegate.poll(java.time.Duration.ofMillis(ms));\n}\n\npublic void close() {\n    closed = true;\n    delegate.close();\n}","typeGuard":"null","tryCatchPattern":"try {\n    consumer.poll(java.time.Duration.ofMillis(500));\n} catch (IllegalStateException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"already been closed\")) {\n        // consumer is unusable; mark it as dead and recreate if needed\n        log.warn(\"Share consumer was closed; skipping further use\", e);\n    } else {\n        throw e;\n    }\n}","preventionTips":["Prefer try-with-resources (ShareConsumer extends AutoCloseable) to make closure explicit and deterministic.","After calling close(), null out the reference so stale holders cannot invoke it again.","In multi-owner code, centralize ownership so only one component owns the lifecycle."],"tags":["share-consumer","lifecycle","close","kafka-client"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}