Your Spring Boot Transaction Doesn't Make Event Publishing Reliable
- Get link
- X
- Other Apps
Your Spring Boot Transaction Doesn't Make Event Publishing Reliable
A request marks an order as paid and then publishes an OrderPaid event. Both calls may succeed in a normal test. But the database and a message broker are separate systems: the database can commit while the broker is unavailable, or the broker can accept a message just before the database transaction rolls back. A Spring @Transactional method does not, by itself, make those two systems one atomic transaction.
This gap is a common source of events that are missing, duplicated, or inconsistent with the business record. A transactional outbox narrows the gap by making the business change and the intent to publish an event part of the same database transaction.
Write the event intent with the business change
Instead of calling the broker inside the request's database transaction, insert a durable outbox row alongside the order update:
BEGIN; UPDATE orders SET status = 'PAID' WHERE id = :order_id; INSERT INTO outbox_events (id, aggregate_type, aggregate_id, event_type, payload) VALUES (:event_id, 'Order', :order_id, 'OrderPaid', :payload); COMMIT; The placeholders are illustrative; bind values through your database library rather than assembling SQL strings. If either database write fails, the transaction rolls back both. If it commits, the event record is durable even if the application stops before contacting the broker. PostgreSQL describes this all-or-nothing property as transaction atomicity.
A separate relay reads committed outbox records and publishes them. It can poll the table or consume database changes through change data capture (CDC). The request path no longer has to keep a database transaction open while waiting on a network call.
Pick a relay that fits your application
- Polling publisher: A worker claims a small batch of pending rows, publishes them, then records successful delivery. It is straightforward to reason about and can work well at modest volume. Use short database transactions to claim work; do not hold row locks while waiting for the broker. PostgreSQL's
SKIP LOCKEDoption can help multiple workers avoid claiming the same rows, but it does not by itself provide retries, ordering, or delivery guarantees. - CDC relay: A connector such as Debezium can capture changes to the outbox table from the database change stream. Debezium's outbox event router maps fields such as event ID, aggregate type, aggregate ID, and payload into broker records. This can avoid repeatedly querying a growing table, but it adds connector and database-log operations to own.
- Spring Modulith: For events handled by Spring application modules, its event publication registry records listener publications in the original business transaction and tracks listener completion. That gives the application a persisted record to recover or resubmit after failure. It is an option for Spring application events, not a blanket guarantee for arbitrary external side effects.
Choose one path based on throughput, existing infrastructure, and the recovery model your team can operate. An outbox is not free: it adds durable state, a relay, and operational work.
Assume a message can be delivered more than once
A relay can publish successfully and then crash before recording that success. On restart, it may publish the same row again. That is why an outbox normally provides reliable, at-least-once publication rather than magical exactly-once effects across a database and broker.
Give each event a stable unique ID and make consumers idempotent. A consumer can store processed event IDs with its own database change in one transaction, so receiving the same event again does not apply the business effect twice. If handling the event calls another external service, use that service's idempotency support where available; a local deduplication table cannot make a remote side effect atomic.
Make ordering and recovery explicit
Some events can be processed independently; others must preserve order for a particular order, account, or other aggregate. If order matters, define a per-aggregate sequence or another explicit ordering rule and configure the broker key/partition strategy accordingly. Debezium uses the aggregate ID as the message key in its default outbox routing, which is useful for Kafka partitioning; a key alone does not establish a complete end-to-end ordering policy.
Plan for stuck and malformed messages. Track the age of the oldest pending event, retry counts, relay failures, and backlog size. Decide how to inspect and reprocess a poison message without silently discarding it. Retain or archive delivered rows according to a policy, and keep sensitive data out of event payloads unless consumers truly need it. CDC deployments also need a plan to monitor connector health and database log retention.
When the outbox is worth it
Use this pattern when a committed business change must reliably cause another process to act—for example, publishing an order event or scheduling a durable integration. For nonessential notifications, a simpler best-effort approach may be sufficient. The key is to choose consciously: a transaction around local database writes cannot promise that a separate broker or HTTP service received the corresponding message.
For a Spring application, start by deciding whether Spring Modulith's persisted event publication model fits your listeners. If events must cross a service boundary, compare a polling relay with CDC and document how consumers deduplicate, how ordering works, and how operators recover failures. Those decisions—not the outbox table alone—make the integration dependable.
References
- Chris Richardson, Microservices.io: Transactional Outbox
- PostgreSQL 18 Documentation: Transactions
- Debezium Documentation: Outbox Event Router
- Spring Modulith Reference: Working with Application Events
- PostgreSQL 18 Documentation: Logical Decoding
❤️ Support This Blog
If this post helped you, you can support my writing with a small donation. Thank you for reading.
- Get link
- X
- Other Apps

Comments
Post a Comment