{"id":"2bec9da712bf7dc7","repo":"apache/kafka","slug":"kafkashareconsumer-is-not-safe-for-multi-threaded","errorCode":null,"errorMessage":"KafkaShareConsumer is not safe for multi-threaded access. currentThread(name: ${thread.getName()}, id: ${threadId}) otherThread(id: ${currentThread.get()})","messagePattern":"KafkaShareConsumer is not safe for multi-threaded access\\. currentThread\\(name: (.+?), id: (.+?)\\) otherThread\\(id: (.+?)\\)","errorType":"exception","errorClass":"ConcurrentModificationException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/clients/consumer/internals/ShareConsumerImpl.java","lineNumber":1148,"sourceCode":"            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();\n        final long threadId = thread.getId();\n        if (threadId != currentThread.get() && !currentThread.compareAndSet(NO_CURRENT_THREAD, threadId))\n            throw new ConcurrentModificationException(\"KafkaShareConsumer is not safe for multi-threaded access. \" +\n                    \"currentThread(name: \" + thread.getName() + \", id: \" + threadId + \")\" +\n                    \" otherThread(id: \" + currentThread.get() + \")\"\n            );\n        if (acknowledgementCommitCallbackHandler != null && acknowledgementCommitCallbackHandler.hasEnteredCallback()) {\n            throw new IllegalStateException(\"KafkaShareConsumer methods are not accessible from user-defined \" +\n                    \"acknowledgement commit callback.\");\n        }\n        refCount.incrementAndGet();\n    }\n\n    /**\n     * Release the light lock protecting the consumer from multithreaded access.\n     */\n    private void release() {\n        if (refCount.decrementAndGet() == 0)\n            currentThread.set(NO_CURRENT_THREAD);\n    }\n","sourceCodeStart":1130,"sourceCodeEnd":1166,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/ShareConsumerImpl.java#L1130-L1166","documentation":"Thrown by acquire() (line 1148) as a ConcurrentModificationException when the calling thread's id differs from the thread that currently holds the consumer's single-thread lock. KafkaShareConsumer (like the classic KafkaConsumer) is not thread-safe; acquire() emulates ownership via an AtomicLong holding the owning thread id. Re-entrancy from the same thread is allowed, but a second thread touching the consumer while another is inside an API call is rejected.","triggerScenarios":"Thread A is inside consumer.poll(...) while thread B calls consumer.acknowledge(...), consumer.subscribe(...), consumer.commitSync(...), or any other public method. Also triggered by wakeup() paths that accidentally call into synchronized logic, or by sharing the consumer instance across an executor/pool without serialization.","commonSituations":"Wrapping the consumer in a Spring @Service used concurrently by multiple request threads; passing the consumer to a listener that runs on a different thread than the poll loop; scheduling poll and acknowledge on two separate scheduled executors; reusing a consumer field from both a request handler and a background rebalance listener.","solutions":["Pin the consumer to a single thread — run poll, acknowledge, commit, and close on the same thread (typically a dedicated consumer thread).","If multiple threads must interact with the consumer, route all calls through a single-threaded executor (e.g. Executors.newSingleThreadExecutor) and have other threads submit tasks to it.","Use consumer.wakeup() (the only thread-safe method) to break a poll loop from another thread, then perform state changes on the consumer thread.","If you need parallel consumption, create one KafkaShareConsumer per thread, each with its own group membership or assignment."],"exampleFix":"// before\nExecutorService pool = Executors.newFixedThreadPool(4);\npool.submit(() -> consumer.poll(Duration.ofMillis(500)));\npool.submit(() -> consumer.acknowledge(record)); // throws ConcurrentModificationException\n\n// after\nExecutorService consumerThread = Executors.newSingleThreadExecutor();\nFuture<ConsumerRecords<String,String>> f = consumerThread.submit(() -> consumer.poll(Duration.ofMillis(500)));\nconsumerThread.submit(() -> consumer.acknowledge(record));","handlingStrategy":"validation","validationCode":"// Pin all consumer operations to a single owning thread; cross-thread callers go through a queue.\nprivate final java.util.concurrent.ExecutorService consumerThread =\n    java.util.concurrent.Executors.newSingleThreadExecutor(r -> {\n        Thread t = new Thread(r, \"share-consumer-owner\");\n        t.setDaemon(true);\n        return t;\n    });\n\nprivate <T> java.util.concurrent.CompletableFuture<T> submitOnOwnerThread(java.util.function.Supplier<T> task) {\n    return java.util.concurrent.CompletableFuture.supplyAsync(task, consumerThread);\n}","typeGuard":"null","tryCatchPattern":"try {\n    consumer.poll(java.time.Duration.ofMillis(100));\n} catch (java.util.ConcurrentModificationException e) {\n    // Detected multi-threaded access: route the call through the owning thread instead of retrying in-place.\n    throw new IllegalStateException(\"ShareConsumer accessed from a non-owner thread; serialize calls\", e);\n}","preventionTips":["A ShareConsumer is single-threaded by contract; never share the instance across threads.","To interrupt a poll from another thread, call consumer.wakeup() (which is thread-safe) instead of touching the consumer directly.","If you need fan-out, hand records off to a worker pool but keep all consumer API calls on the owner thread."],"tags":["share-consumer","threading","concurrency","kafka-client"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}