Skip to content
Kafka Is Fun

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.

ExactlyOnceProcessor.javajava
// 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

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.