{"id":"2224a75e1a9bdcaa","repo":"apache/kafka","slug":"kafkaconsumer-is-not-safe-for-multi-threaded-acces","errorCode":null,"errorMessage":"KafkaConsumer is not safe for multi-threaded access. currentThread(name: {}, id: {}) otherThread(id: {})","messagePattern":"KafkaConsumer is not safe for multi-threaded access\\. currentThread\\(name: (.+?), id: (.+?)\\) otherThread\\(id: (.+?)\\)","errorType":"exception","errorClass":"java.util.ConcurrentModificationException","httpStatus":null,"severity":"critical","filePath":"clients/src/main/java/org/apache/kafka/clients/consumer/internals/AsyncKafkaConsumer.java","lineNumber":2229,"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(\"KafkaConsumer is not safe for multi-threaded access. \" +\n                \"currentThread(name: \" + thread.getName() + \", id: \" + threadId + \")\" +\n                \" otherThread(id: \" + currentThread.get() + \")\"\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\n    private void subscribeInternal(Pattern pattern, Optional<ConsumerRebalanceListener> listener) {\n        acquireAndEnsureOpen();\n        try {\n            throwIfGroupIdNotDefined();","sourceCodeStart":2211,"sourceCodeEnd":2247,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/AsyncKafkaConsumer.java#L2211-L2247","documentation":"Thrown by acquire() when the consumer's light-weight single-writer lock detects that the calling thread differs from the thread that currently holds the consumer (and the slot is not free). KafkaConsumer is intentionally not thread-safe; it tracks an owning thread id via an AtomicLong and rejects concurrent access with a ConcurrentModificationException rather than blocking. The message names both threads to make the misuse debuggable.","triggerScenarios":"Two application threads both calling methods on the same KafkaConsumer instance (e.g. a poll thread and a commit thread); waking the consumer via wakeup() is allowed, but any other method called from a non-owner thread throws; shared consumer passed to multiple executors/schedulers.","commonSituations":"Wrapping the consumer in a shared service bean injected into multiple controllers; scheduling commitAsync from a different ScheduledExecutorService than the poll loop; Spring Kafka ListenerContainer sharing a consumer with application code; migrations from a queue library that permitted concurrent consumers.","solutions":["Confine all consumer method calls to a single dedicated thread; have other threads submit work via a queue and call consumer.wakeup() (the only thread-safe method) to unblock poll.","If you need parallel consumption, create one KafkaConsumer instance per thread, each joining the same group.","Move commit calls into the poll-loop thread (use auto-commit or commitAsync inside the loop) rather than a separate thread.","Wrap the consumer in a thread-affine accessor that asserts Thread.currentThread() == ownerThread before delegating."],"exampleFix":"// before\nKafkaConsumer<String,String> c = new KafkaConsumer<>(props);\nExecutorService es = Executors.newFixedThreadPool(2);\nes.submit(() -> c.poll(Duration.ofMillis(100)));\nes.submit(() -> c.commitSync()); // throws ConcurrentModificationException\n\n// after\nKafkaConsumer<String,String> c = new KafkaConsumer<>(props);\nThread consumerThread = new Thread(() -> {\n    while (!closed) {\n        ConsumerRecords<String,String> recs = c.poll(Duration.ofMillis(100));\n        // process, then commit on THIS thread\n        if (!recs.isEmpty()) c.commitSync();\n    }\n});\nconsumerThread.start();\n// other threads only call c.wakeup() to signal shutdown","handlingStrategy":"validation","validationCode":"// Enforce single-thread ownership at the application level:\nprivate final Thread ownerThread = Thread.currentThread();\nprivate void ensureOwner() {\n    if (Thread.currentThread() != ownerThread)\n        throw new IllegalStateException(\"Consumer accessed from non-owner thread\");\n}\n// Call ensureOwner() before every consumer method.\n// For cross-thread wake-up, use consumer.wakeup() only — it is thread-safe.","typeGuard":"// Java: bind consumer to a single thread\nstatic boolean isOwnerThread(Thread owner) {\n    return Thread.currentThread() == owner;\n}","tryCatchPattern":"try {\n    consumer.poll(Duration.ofMillis(100));\n} catch (ConcurrentModificationException e) {\n    log.error(\"Consumer used from multiple threads\", e);\n    // Do NOT retry on the same thread — fix the design instead.\n    throw e;\n}","preventionTips":["KafkaConsumer is NOT thread-safe. Create it on, and use it only from, one thread.","For multi-thread consumption, run one KafkaConsumer per thread (or use Kafka Streams).","To interrupt a poll from another thread, call consumer.wakeup() — that is the only thread-safe method.","Wrap the consumer in a thread-affine facade whose methods assert Thread.currentThread() == ownerThread.","Never put a KafkaConsumer in a shared singleton, Spring @Component with singleton scope, or static field accessed by multiple threads."],"tags":["consumer","thread-safety","concurrent-modification","multi-threaded","java"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}