Advanced Patterns · Advanced · Topic 4 of 5
Kafka and Database Transactions Together
Spring runs a Kafka transaction in the listener container and a database transaction from @Transactional. They are synchronized, not atomic: the database commits first and then Kafka. If the Kafka commit fails, the record is redelivered, so the database update must be safe to repeat. Inside Kafka alone, a container transaction gives read-process-write exactly-once: the offsets are sent to the transaction.
// With spring.kafka.producer.transaction-id-prefix set, Spring Boot gives the listener container a
// KafkaTransactionManager. The container starts a Kafka transaction before the listener runs and,
// when the listener returns, sends the consumed offsets to that transaction and commits it.
// @Transactional adds the database's own transaction, committed first.
@Service
public class ExactlyOnceProcessor {
// "dstm" is the name of your database transaction manager bean
@Transactional("dstm")
@KafkaListener(topics = "payments.payment.completed")
public void processPayment(PaymentEvent event) {
// Idempotent database write: this method can run again if the Kafka commit fails
Order order = orderRepo.findById(event.getOrderId()).orElseThrow();
order.setStatus("PAID");
orderRepo.save(order);
// Sent inside the container's Kafka transaction
kafkaTemplate.send("orders.order.paid",
event.getOrderId(), new OrderPaidEvent(order));
}
}Sources
- Transactions (Spring for Apache Kafka) (opens in a new tab)
- Exactly Once Semantics (Spring for Apache Kafka) (opens in a new tab)
- Tips, tricks and examples (Spring for Apache Kafka) (opens in a new tab)
Checked against Spring for Apache Kafka 4.1.1, Spring Boot 4.1.1 and Kafka 4.2 documentation on 11 October 2026. Draft: not yet reviewed by a Kafka specialist, and releases move on, so confirm against the current documentation before you act on it.