{"id":"2accaeaff19c3c16","repo":"apache/kafka","slug":"cannot-add-partition-topicpartition-to-transacti-2accae","errorCode":null,"errorMessage":"Cannot add partition {topicPartition} to transaction while in state  {currentState}","messagePattern":"Cannot add partition (.+?) to transaction while in state  (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/clients/producer/internals/TransactionManager.java","lineNumber":474,"sourceCode":"                            .setGroupId(groupMetadata.groupId())\n            );\n            handler = new AddOffsetsToTxnHandler(builder, offsets, groupMetadata);\n        }\n\n        enqueueRequest(handler);\n        return handler.result;\n    }\n\n    public synchronized void maybeAddPartition(TopicPartition topicPartition) {\n        maybeFailWithError();\n        throwIfPendingState(TransactionOperation.SEND);\n\n        if (isTransactional()) {\n            if (!hasProducerId()) {\n                throw new IllegalStateException(\"Cannot add partition \" + topicPartition +\n                    \" to transaction before completing a call to initTransactions\");\n            } else if (currentState != State.IN_TRANSACTION) {\n                throw new IllegalStateException(\"Cannot add partition \" + topicPartition +\n                    \" to transaction while in state  \" + currentState);\n            } else if (isTransactionV2Enabled()) {\n                txnPartitionMap.getOrCreate(topicPartition);\n                partitionsInTransaction.add(topicPartition);\n                transactionStarted = true;\n            } else if (transactionContainsPartition(topicPartition) || isPartitionPendingAdd(topicPartition)) {\n                return;\n            } else {\n                log.debug(\"Begin adding new partition {} to transaction\", topicPartition);\n                txnPartitionMap.getOrCreate(topicPartition);\n                newPartitionsInTransaction.add(topicPartition);\n            }\n        }\n    }\n\n    RuntimeException lastError() {\n        return lastError;\n    }","sourceCodeStart":456,"sourceCodeEnd":492,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/clients/producer/internals/TransactionManager.java#L456-L492","documentation":"Thrown by TransactionManager.maybeAddPartition when the producer is transactional and has a producerId, but currentState is not IN_TRANSACTION. A partition can only be added to an open transaction; calling send() outside beginTransaction()..commit/abort would produce records outside any transaction. The guard preserves EOS guarantees and, because it throws on the application thread, leaves the state machine recoverable.","triggerScenarios":"Calling producer.send() in the READY state (after init but before beginTransaction), or in COMMITTING/ABORTING/ABORTABLE_ERROR/FATAL_ERROR. The state machine check at line 473-475 fires and reports the offending state in the message.","commonSituations":"Mis-ordered transaction lifecycle (send before beginTransaction or after commit); code paths that skip beginTransaction on retry; an aborted transaction followed by sends without a new beginTransaction; background senders that race with the transaction boundary.","solutions":["Wrap every send() in a beginTransaction()..commitTransaction() block.","After abortTransaction() returns, call beginTransaction() again before any further send — the producer is back in READY.","Serialise all transactional lifecycle calls on a single thread or guard them with a lock to avoid a background sender escaping the bracket.","Inspect currentState reported in the message — it pinpoints which lifecycle step was missed or repeated."],"exampleFix":"// before\nproducer.initTransactions();\nproducer.send(new ProducerRecord<>(\"t\", \"k\", \"v\")); // throws: state READY\nproducer.commitTransaction();\n\n// after\nproducer.initTransactions();\nproducer.beginTransaction();\nproducer.send(new ProducerRecord<>(\"t\", \"k\", \"v\"));\nproducer.commitTransaction();","handlingStrategy":"validation","validationCode":"// Track whether a transaction is open; only allow send() while it is.\nprivate volatile boolean inTransaction = false; // true between begin and commit/abort\n...\nif (!inTransaction) throw new IllegalStateException(\"begin a transaction before sending\");\nproducer.send(record, callback);","typeGuard":null,"tryCatchPattern":"try {\n    producer.send(record, callback);\n} catch (IllegalStateException ise) {\n    // not in IN_TRANSACTION state; call beginTransaction() (or abort a bad txn) and retry\n}","preventionTips":["Call producer.beginTransaction() exactly once per transaction before any send().","Commit or abort before beginning the next transaction; overlapping transactions are not supported.","Mirror transaction state in your own variable since KafkaProducer does not expose it."],"tags":["producer","transactions","eos","state-machine","java"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}