{"id":"4aca42533605c3b0","repo":"apache/kafka","slug":"this-consumer-has-already-been-closed-4aca42","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/ClassicKafkaConsumer.java","lineNumber":1237,"sourceCode":"        // are partitions with a missing position, then we will raise an exception.\n        subscriptions.resetInitializingPositions();\n\n        // Finally send an asynchronous request to look up and update the positions of any\n        // partitions which are awaiting reset.\n        offsetFetcher.resetPositionsIfNeeded();\n\n        return true;\n    }\n\n    /**\n     * Acquire the light lock and ensure that the consumer hasn't been closed.\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 multi-threaded access. Instead of blocking\n     * when the lock is not available, however, we just throw an exception (since multi-threaded usage is not\n     * supported).\n     * @throws ConcurrentModificationException if another thread already has the lock\n     */\n    private void acquire() {\n        final Thread thread = Thread.currentThread();\n        final long threadId = thread.getId();","sourceCodeStart":1219,"sourceCodeEnd":1255,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/ClassicKafkaConsumer.java#L1219-L1255","documentation":"Thrown by acquireAndEnsureOpen() whenever any consumer operation is invoked after close() has set the closed flag. The consumer is single-use for open operations, so once closed it rejects all public API calls with this IllegalStateException. It protects against use-after-close in shared or pooled consumers.","triggerScenarios":"Calling poll(), commitSync(), position(), subscribe(), etc. after consumer.close(); a shutdown thread closing the consumer while another thread still calls it; dependency injection scopes returning a closed bean; try-with-resources then reusing the consumer.","commonSituations":"Multi-threaded code where a background thread closes the consumer; Spring/lifecycle beans where @PreDestroy fired but a scheduled task still runs; try-with-resources blocks leaking references; frameworks (Camel, Spring Cloud Stream) mismanaging consumer lifecycle on restart.","solutions":["Create a new KafkaConsumer instance after closing; do not reuse a closed consumer.","Synchronize close and usage so no operation runs after close; use a single owner thread for the consumer lifecycle.","Guard pooled consumers with an 'open' flag and re-instantiate on demand instead of reopening.","Fix lifecycle wiring in DI frameworks to ensure tasks stop before @PreDestroy."],"exampleFix":"// before\nconsumer.close();\nconsumer.poll(Duration.ofMillis(100)); // throws\n\n// after\nconsumer.close();\nconsumer = new KafkaConsumer<>(props); // new instance\nconsumer.poll(Duration.ofMillis(100));","handlingStrategy":"type-guard","validationCode":"// Track lifecycle yourself so you never call a method on a closed consumer:\nprivate volatile boolean closed = false;\npublic synchronized void close() {\n    if (closed) return;\n    closed = true;\n    consumer.close();\n}\npublic long position(TopicPartition tp) {\n    if (closed) throw new IllegalStateException(\"consumer already closed\");\n    return consumer.position(tp);\n}","typeGuard":"// A wrapper type whose state machine makes post-close calls unrepresentable:\nfinal class ManagedConsumer<K,V> implements AutoCloseable {\n    private KafkaConsumer<K,V> consumer; // null once closed\n    public synchronized long position(TopicPartition tp) {\n        KafkaConsumer<K,V> c = consumer;\n        if (c == null) throw new IllegalStateException(\"closed\");\n        return c.position(tp);\n    }\n    public synchronized void close() {\n        if (consumer != null) { consumer.close(); consumer = null; }\n    }\n}","tryCatchPattern":"// If you receive this from an opaque consumer handle, downgrade to a no-op for the closed case:\ntry {\n    consumer.position(tp);\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"already been closed\")) {\n        log.debug(\"Consumer closed; ignoring position() call\");\n        return -1L;\n    }\n    throw e;\n}","preventionTips":["Use try-with-resources on the consumer (it's AutoCloseable) so close happens exactly once at a well-defined scope.","Never share a consumer across threads or re-enter it from a callback that fires after close(); the closed flag is set inside close().","In async pipelines, gate every consumer interaction behind an AtomicBoolean isOpen so a race between shutdown and a worker doesn't hit this error."],"tags":["consumer","lifecycle","use-after-close","java"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}