← Back to list

A Guide to the Outbox Pattern: Why @Transactional Isn’t Enough for Kafka in Microservices…

Stop losing data between your DB and Kafka. Learn why @Transactional doesn't roll back Kafka messages and how to implement the Outbox…

Sudha Subramaniam in Towards AWS · 2026-03-09 03:49 · 27 claps · 4.7 min read paywalled
#outbox-pattern #transactional-outbox #microservices #kafka #spring-boot
Open on Medium ↗

A Guide to the Outbox Pattern: Why @Transactional Isn’t Enough for Kafka in Microservices Architecture

Stop losing data between your DB and Kafka. Learn why @Transactional doesn't roll back Kafka messages and how to implement the Outbox Pattern in Spring Boot for 100% consistency

Does @Transactional Cover Both DB and Kafka? No. In most applications, @Transactional only controls the database transaction, not Kafka. It works only with the transaction manager configured for that resource.

By default, @Transactional only manages the database, not Kafka.

Spring Boot typically auto-configures a JPATransactionManager for the DB.

This manager has no native control over external Kafka brokers or clusters.

If a DB rollback occurs, a message sent to Kafka remains sent (inconsistency).

Kafka operates on its own separate transaction protocol and infrastructure.

To link them, you must explicitly configure a KafkaTransactionManager.

Even with both, they remain two separate “units of work” by default.

The Outbox Pattern is the preferred way to ensure both sync perfectly.

In that pattern, you save the Kafka message to a DB table first.

This ensures the message only exists if the DB transaction successfully commits.

@Transactional
public void transfer() {
    transferRepository.save(transfer);
    kafkaTemplate.send("transfer-topic", event);
    throw new RuntimeException();  // (3) Crash!
}
Start DB Transaction
    |
    +-- Save transfer to DB
    |
    +-- Send message to Kafka (outside DB transaction)
Commit DB Transaction

What the Outbox Pattern Actually Solves ? The Outbox pattern ensures that database updates and event publishing stay consistent, even if failures occur.

Instead of writing to the database and Kafka separately, the service writes both the business data and the event into the same database transaction.

BEGIN TRANSACTION
Insert into transfers
Insert into outbox_events
COMMIT

both executed in same transaction boundary ,This guarantees that events cannot be lost.

When I first learned about the Outbox Pattern, the idea sounded reasonable to me — store the event in a separate table along with the business transaction so we never lose events.

But my first reaction was also very practical

Wait, now we are adding another table and another process. Isn’t that introducing more operational headache?

That thought is actually valid. While the Outbox pattern solves a very important reliability problem , it also introduces some trade-offs that engineers must understand before adopting it.

How the Event Eventually Reaches Kafka

The event stored in the Outbox table is not published immediately.

Instead, a separate process reads the outbox table and publishes events to Kafka.

Application
     |
     v
Database Transaction
     |
     +---- transfers table
     |
     +---- outbox_events table
             |
             v
      Outbox Publisher
             |
             v
            Kafka

Once the event is successfully published, the record is marked as processed.

Why Not Use Distributed Transactions?

One alternative is distributed transactions (2-phase commit) across Database and Kafka.

But this approach has major problems:

  • Complex to implement
  • Slower performance
  • Not supported by many systems
  • Reduces system availability

Trade-offs of the Outbox Pattern

While the Outbox pattern solves reliability issues, it introduces some trade-offs.

Let’s walk through those trade-offs in a practical way.

Additional System Complexity

Without the Outbox pattern, the code flow is very straightforward.

Save the record to DB

publish event to kafka

With the Outbox pattern, the architecture changes slightly.

Instead of publishing directly to Kafka, the service first writes the event to an Outbox table inside the same transaction as the business data.

Later, another component reads the Outbox table and publishes events to Kafka.

The flow becomes

Application
   |
   +-- Business Table (transfers)
   |
   +-- Outbox Table (events)
           |
           v
      Outbox Publisher
           |
           v
          Kafka

Now the system needs to manage additional pieces such as

  • the Outbox table itself
  • a publisher process that reads events
  • retry mechanisms if publishing fails
  • cleanup jobs to remove processed events

So while the pattern improves reliability, it does increase the architectural complexity of the system.

Eventual Consistency

Events are not published instantly. There may be a small delay between Transaction commit and Event appearing in Kafka.

Without the Outbox pattern, the event is usually published immediately after the database write.

With the Outbox pattern, the flow looks like this

Transaction commits
↓
Event stored in Outbox table
↓
Publisher reads Outbox
↓
Event sent to Kafka

This introduces a small delay between Database update and Event reaching Kafka , the system becomes eventually consistent rather than instantly consistent.

For most systems this delay is tiny (milliseconds or seconds), but this needs to be taken into account when designing.

Additional Database Load

Another side effect is increased database activity.

Earlier, a transaction might perform just one write — save record

With the Outbox pattern, each business operation performs two writes — Insert transfer and Insert outbox event

Under high traffic, this means:

  • more database writes
  • a rapidly growing outbox table
  • increased indexing and storage requirements

Systems often handle this by using batch publishing and efficient indexing strategies.

Outbox Table Growth

Because every event is first stored in the database, the Outbox table can grow quickly.

If old events are never removed, the table may eventually become very large and impact performance.

Production systems usually implement mechanisms such as:

  • scheduled cleanup jobs
  • time-to-live (TTL) policies
  • archiving old events

Without proper maintenance, the Outbox table can turn into a database bottleneck over time.

Possibility of Duplicate Events

If the publisher crashes during publishing, the event may be retried. This means consumers must handle duplicate events using idempotency.

For example

Event sent to Kafka
Publisher crashes before marking event processed

When the publisher restarts, it may read the same event again and publish it again.

This means downstream systems may receive duplicate events.

Because of this, consumers of the event must implement idempotent processing, meaning they can safely handle the same event multiple times.

Additional Infrastructure

In many production systems, teams do not implement the publisher manually. Instead, they use Change Data Capture (CDC) tools.

A common architecture looks like this

Database
   |
   v
Outbox Table
   |
   v
Debezium (CDC)
   |
   v
Kafka

Tools like Debezium automatically detect changes in the Outbox table and stream them to Kafka.

While this simplifies development, it introduces additional operational infrastructure that must be deployed and maintained.

When the Trade-off Is Worth It

Despite these trade-offs, the Outbox pattern is widely used in systems where reliability is critical.

It becomes particularly valuable when

  • event loss is unacceptable
  • services communicate heavily through events
  • database state and events must remain consistent

Common domains

  • Banking transactions
  • Payment systems
  • Order management
  • Inventory updates
  • Audit systems

In these systems, data consistency is far more important than architectural simplicity

Conclusion

The Outbox Pattern solves a critical problem in microservices ensuring that database updates and events remain consistent, even when failures occur.

However, that reliability comes with some trade-offs

  • additional architectural complexity
  • eventual consistency
  • extra database load
  • handling duplicate events
  • operational overhead

In practice, teams adopt the Outbox pattern when guaranteeing reliable event delivery is more important than keeping the system simple.

If you found this helpful, don’t forget to give this article a clap 👏 and follow me for more tips and insights! Your support means a lot.


메타데이터
post_id
d2f8ce3dda5d
slug
a-guide-to-the-outbox-pattern-why-transactional-isnt-enough-for-kafka-in-microservices-d2f8ce3dda5d
url
https://towardsaws.com/a-guide-to-the-outbox-pattern-why-transactional-isnt-enough-for-kafka-in-microservices-d2f8ce3dda5d
canonical_url
https://towardsaws.com/a-guide-to-the-outbox-pattern-why-transactional-isnt-enough-for-kafka-in-microservices-d2f8ce3dda5d
author_url
https://medium.com/@sudhass
status
ok
fetched_at
2026-06-15 22:55:51