GoodLookRetail BlogEmber
Technology

Spring Boot Transaction Management

A practical guide to @Transactional, database consistency, Kafka event publishing,Redis and reliable transaction patterns in microservices

V
Vivek Kumar
21 August 2026 · 7 min read
8 Views0 Comments7 Min Read
Spring Boot Transaction Management

1. Introduction

Transaction management is one of the most critical aspects of building reliable Spring Boot applications. In a simple monolithic application, managing a transaction can appear straightforward: execute a few database operations inside a @Transactional method and either commit everything or roll everything back.

However, modern enterprise applications rarely depend on a single database.

A typical Spring Boot microservice may interact with multiple infrastructure components:

                         Spring Boot Service
                                │
             ┌──────────────────┼──────────────────┐
             │                  │                  │
             ▼                  ▼                  ▼
        PostgreSQL            Redis              Kafka
        Persistent            Cache              Events
           Data               State              Messaging

Now consider a simple business operation such as placing an order:

Client
  │
  ▼
Order Service
  │
  ├── Create Order ───────────► PostgreSQL
  │
  ├── Reserve Inventory ──────► Inventory Service
  │
  ├── Process Payment ─────────► Payment Service
  │
  ├── Update Cache ────────────► Redis
  │
  └── Publish Event ───────────► Kafka

What happens if the order is successfully stored in PostgreSQL, but Kafka fails to publish the event?

Or if the payment succeeds but inventory reservation fails?

Or if PostgreSQL commits successfully but Redis still contains stale data?

This is where transaction management becomes significantly more complicated.

2. What Does a Transaction Actually Guarantee?

  • ACID

  • Atomicity

  • Consistency

  • Isolation

  • Durability

  • What Spring manages vs what the database manages

3. Understanding @Transactional

@Transactional
public void processOrder() {
    saveOrder();
    updateInventory();
}

Explain:

  • Transaction boundary

  • Commit

  • Rollback

  • Proxy mechanism

  • Runtime vs checked exceptions

4. Transaction Propagation

In Spring Boot, transaction propagation defines what happens when a transactional method calls another transactional method.

The key question is:

If method A is already running inside a transaction and calls method B, should B join A's transaction, create a new transaction, run without a transaction, or behave differently?


| Propagation     | Existing transaction? | Behavior                      |
| --------------- | --------------------- | ----------------------------- |
| `REQUIRED`      | Yes                   | Join existing (By Default)    |
| `REQUIRED`      | No                    | Create new                    |
| `REQUIRES_NEW`  | Yes                   | Suspend existing + create new |
| `SUPPORTS`      | Yes                   | Join existing                 |
| `SUPPORTS`      | No                    | Run without transaction       |
| `MANDATORY`     | Yes                   | Join existing                 |
| `MANDATORY`     | No                    | Throw exception               |
| `NOT_SUPPORTED` | Yes                   | Suspend existing              |
| `NOT_SUPPORTED` | No                    | Run without transaction       |
| `NEVER`         | Yes                   | Throw exception               |
| `NEVER`         | No                    | Run without transaction       |
| `NESTED`        | Yes                   | Nested transaction/savepoint  |
| `NESTED`        | No                    | Create transaction            |
| --------------- | --------------------- | ----------------------------- |

Especially explain:

@Transactional
public void placeBet() {
    walletService.debit();
    auditService.saveAudit();
}
T1: placeBet
 │
 ├── wallet.debit()
 │
 ├── suspend T1
 │
 ├── T2: saveAudit()
 │       │
 │       └── COMMIT
 │
 ├── resume T1
 │
 └── COMMIT / ROLLBACK
So T2 is independent.

5. Transaction Isolation

Explain:

READ_UNCOMMITTED
READ_COMMITTED
REPEATABLE_READ
SERIALIZABLE

Then demonstrate:

Transaction A
      │
      ├── Read
      │
      │       Transaction B
      │             │
      │             └── Update
      │
      └── Read again

Cover:

  • Dirty read

  • Non-repeatable read

  • Phantom read

  • Lost update


6. Spring Boot + PostgreSQL Transaction Example

Build a realistic example:

POST /orders
      │
      ▼
OrderService
      │
      ├── Create Order
      ├── Reserve Inventory
      └── Create Payment Transaction
             │
             ▼
          COMMIT

Show the actual Spring Boot implementation.


7. Transaction Management with Kafka

This should be one of the main sections.

Explain the problem:

DB Transaction
     │
     ▼
Save Order
     │
     ▼
Kafka Publish

What happens if:

DB COMMIT succeeds
Kafka publish fails

or:

Kafka publish succeeds
DB COMMIT fails

This creates inconsistency.

Explain Kafka transaction support

Spring Boot
     │
     ├── Database
     │
     └── Kafka

Discuss:

  • Kafka producer transactions

  • KafkaTransactionManager

  • @Transactional

  • transactional.id

  • Exactly-once semantics

  • Consumer transactions

  • Offset management

Then explain an important limitation:

A Kafka transaction and a PostgreSQL transaction are not automatically one atomic transaction.

This leads naturally into the Outbox Pattern.


8. The Outbox Pattern

This should be a major practical section.

Instead of:

DB
 │
 └──► Kafka

use:

             PostgreSQL
                  │
        ┌─────────┴─────────┐
        ▼                   ▼
     Business             Outbox
       Data                Event
        │                   │
        └──── Same DB TX ───┘
                            │
                            ▼
                      Outbox Publisher
                            │
                            ▼
                          Kafka

Example:

@Transactional
public void createOrder(OrderRequest request) {

    Order order = orderRepository.save(...);

    outboxRepository.save(
        new OutboxEvent(
            order.getId(),
            "ORDER_CREATED"
        )
    );
}

Then a publisher sends the outbox event to Kafka.

Explain:

  • Why it works

  • Retry

  • Idempotency

  • Duplicate events

  • Polling publisher

  • CDC/Debezium alternative


9. Redis and Transactions

This is another area where developers commonly make mistakes.

Explain why this is dangerous:

PostgreSQL Transaction
        │
        ▼
Update DB
        │
        ▼
Update Redis

If Redis fails after DB commit:

Database = NEW
Redis    = OLD

Explain Redis's own transaction mechanisms:

MULTI
EXEC
WATCH

But make the distinction:

Redis transactions do not automatically participate in your PostgreSQL @Transactional transaction.


10. Cache-Aside Transaction Pattern

Show the recommended pattern:

              Request
                 │
                 ▼
             PostgreSQL
                 │
              COMMIT
                 │
                 ▼
          Invalidate Cache
                 │
                 ▼
               Redis

Example:

@Transactional
public void updateProduct(Product product) {
    repository.save(product);
}

Then after successful commit:

@TransactionalEventListener(
    phase = TransactionPhase.AFTER_COMMIT
)
public void evictCache(ProductUpdatedEvent event) {
    redis.delete(event.productId());
}

This is a very useful production pattern.


11. Distributed Transactions

Explain the problem with:

Service A
   │
   ▼
Service B
   │
   ▼
Service C

Each service owns its own database.

You cannot simply assume:

@Transactional

will roll back changes made in another service.

Explain:

Option 1 — Two-Phase Commit

Coordinator
   │
   ├── Service A
   ├── Service B
   └── Service C

Explain:

  • Prepare

  • Commit

  • Rollback

  • Advantages

  • Performance

  • Availability concerns

Option 2 — Saga Pattern

Order Service
      │
      ▼
Payment Service
      │
      ▼
Inventory Service

If Inventory fails:

Inventory FAILED
       │
       ▼
Compensating Action
       │
       ▼
Refund Payment
       │
       ▼
Cancel Order

For modern microservices, this deserves significant attention.


12. Distributed Transaction Example

Use a realistic e-commerce example:

Order Service
     │
     ├── Create Order
     │
     ▼
Payment Service
     │
     ├── Charge Payment
     │
     ▼
Inventory Service
     │
     └── Reserve Stock

Failure:

Order       = CREATED
Payment     = SUCCESS
Inventory   = FAILED

Saga compensation:

Inventory FAILED
       │
       ▼
Payment REFUND
       │
       ▼
Order CANCELLED

This is much easier to understand than purely theoretical distributed transaction examples.


13. Common @Transactional Mistakes

This section can be particularly valuable.

Mistake 1 — Self Invocation

public void methodA() {
    methodB();
}

@Transactional
public void methodB() {
}

methodB() may not pass through the Spring proxy.

Mistake 2 — Private Transactional Methods

@Transactional
private void process() {
}

Don't expect normal Spring proxy-based transaction behavior here.

Mistake 3 — Catching Exceptions

@Transactional
public void process() {
    try {
        save();
    } catch (Exception e) {
        log.error("Failed", e);
    }
}

The exception is swallowed, so rollback behavior may not be what you expect.

Mistake 4 — Long Transactions

Avoid:

BEGIN
 │
 ├── DB
 ├── HTTP API
 ├── Kafka
 ├── External Payment
 ├── Complex Processing
 │
COMMIT

External calls inside DB transactions can hold locks/connections unnecessarily.

Mistake 5 — Assuming @Transactional Covers Kafka

It doesn't automatically make PostgreSQL + Kafka one atomic transaction.

Mistake 6 — Assuming Redis Rolls Back

Redis changes don't automatically roll back when PostgreSQL rolls back.

Mistake 7 — Wrong Transaction Boundary

Put transactions around the business operation, not arbitrary repository calls.

Mistake 8 — Calling External APIs Inside Transactions

@Transactional
public void payment() {

    saveOrder();

    paymentProvider.call(); // risky

    updateOrder();
}

The external call may be slow or timeout while your DB transaction remains open.


14. Production Best Practices

This should be your strongest section.

Keep transactions short

BEGIN
  │
  ├── Validate
  ├── DB operations
  └── COMMIT

Avoid long-running business processes.

Define clear ownership

Each microservice should generally own its database.

Order Service  → Order DB
Payment Service → Payment DB
Inventory Service → Inventory DB

Prefer Outbox for DB → Kafka

Business Data + Outbox
          │
       Same TX
          │
          ▼
        Kafka

Use idempotency

Every important distributed operation should have a unique business/transaction ID.

Use optimistic locking where appropriate

@Version
private Long version;

Use pessimistic locking selectively

Don't lock everything.

Avoid distributed locks unless necessary

Prefer database constraints and transactional design where possible.

Monitor transaction behavior

Monitor:

  • Transaction duration

  • DB connection pool

  • Lock waits

  • Deadlocks

  • Rollbacks

  • Kafka lag

  • Outbox backlog

  • Redis latency


15. Recommended Architecture

End the article with a complete architecture:

                         API
                          │
                          ▼
                  ┌───────────────┐
                  │ Spring Boot   │
                  │ Service       │
                  └───────┬───────┘
                          │
                  @Transactional
                          │
             ┌────────────┼────────────┐
             ▼            ▼            ▼
        PostgreSQL      Outbox       Redis
             │            │
             │         Same TX
             │            │
             │            ▼
             │          Kafka
             │            │
             │      ┌─────┼─────┐
             │      ▼     ▼     ▼
             │   Service Service Service
             │      A     B     C
             │
             ▼
       Consistent State

And explain:

PostgreSQL provides local transactional consistency, Redis provides fast state/cache access, Kafka provides asynchronous event propagation, and the Outbox/Saga patterns provide reliable coordination across distributed services.


16. Conclusion

The key message of the article should be:

@Transactional is powerful, but it is not a distributed transaction mechanism.

For production Spring Boot systems:

Local DB consistency
        ↓
@Transactional
        ↓
Concurrent update protection
        ↓
Optimistic/Pessimistic locking
        ↓
DB → Kafka reliability
        ↓
Outbox Pattern
        ↓
Service → Service consistency
        ↓
Saga Pattern
        ↓
Idempotency + Observability

Promote this post

Share & amplify

LinkedIn post generator

Generate a polished, professional LinkedIn post from this article. Edit before posting.

Comments (0)

Sort:
Sign in to join the discussion.