Spring Data
Transaction Management
Retry Mechanism
Rollback
Java Development

Spring Data rollback transaction on retry

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

In Spring Data services, retry and transaction behavior must be designed together. If retry attempts run inside the same failed transaction context, rollback behavior becomes confusing and side effects may leak. A reliable setup starts a fresh transaction per attempt, defines rollback rules explicitly, and keeps retried operations idempotent.

Understand Transaction and Retry Boundaries

Retries should generally happen around transactional method execution, not inside one long transaction.

Typical intent:

  1. Attempt one runs in transaction one.
  2. Failure triggers rollback of transaction one.
  3. Attempt two runs in new transaction two.

This model keeps failures isolated.

Basic Annotation Setup

Use @Retryable with @Transactional on service entry method.

java
1import org.springframework.dao.TransientDataAccessException;
2import org.springframework.retry.annotation.Backoff;
3import org.springframework.retry.annotation.Recover;
4import org.springframework.retry.annotation.Retryable;
5import org.springframework.stereotype.Service;
6import org.springframework.transaction.annotation.Transactional;
7
8@Service
9public class PaymentService {
10
11    @Retryable(
12        value = {TransientDataAccessException.class},
13        maxAttempts = 3,
14        backoff = @Backoff(delay = 200)
15    )
16    @Transactional
17    public void processPayment(Long id) {
18        // database work
19        // throw TransientDataAccessException to trigger retry
20    }
21
22    @Recover
23    public void recover(TransientDataAccessException ex, Long id) {
24        // fallback path
25    }
26}

Also enable retry support in configuration with @EnableRetry.

Configure Rollback Rules Explicitly

Spring rolls back on unchecked exceptions by default. If checked exceptions should roll back, configure rollbackFor.

java
1@Transactional(rollbackFor = Exception.class)
2public void executeFlow() throws Exception {
3    // business logic
4}

Mismatch between thrown exception types and rollback rules is a common cause of partial commits.

Handle Proxy Boundaries Correctly

Retry and transaction annotations rely on Spring proxies. Internal method calls inside same class can bypass proxies and disable expected behavior.

Move annotated methods to separate beans or call through proxied components when necessary.

This is critical in legacy service classes with self-invocation patterns.

Keep Retried Operations Idempotent

Retries can duplicate writes or external calls if operation is not idempotent. Protect with:

  • Unique business keys.
  • State transition guards.
  • Outbox deduplication identifiers.

Idempotency is required even when rollback works correctly because external systems may not roll back with your database.

Isolate External Side Effects

Do not combine non-idempotent external API calls and DB writes in one naive retry loop. Use outbox or compensating workflows so retries do not duplicate external side effects.

A practical pattern:

  1. Commit business event to outbox in transaction.
  2. Publish externally from outbox worker.
  3. Retry publish with idempotency key.

This keeps transaction and integration concerns separated.

Add Observability for Retry Behavior

Without telemetry, retry issues are hard to debug. Log attempt number and exception class, and expose metrics such as retry count and recover count.

java
// pseudo log message
// retry payment id=123 attempt=2 error=TransientDataAccessException

Alerting on retry spikes helps detect dependency instability early.

Integration Tests for Correctness

Test matrix should include:

  • One transient failure then success.
  • Repeated transient failure reaching recover path.
  • Non-retryable exception path.
  • Checked exception rollback behavior.

These tests confirm both retry semantics and final database state.

Operational Tuning

Set conservative max attempts and backoff. Over-aggressive retries can create retry storms during outages.

Combine retries with circuit breakers and timeout policies. Retry is one resilience tool, not full failure strategy.

Document retryable exceptions in one shared policy to keep behavior consistent across services.

Common Pitfalls

  • Retrying inside one transaction instead of per-attempt transaction scope.
  • Forgetting rollback rules for checked exceptions.
  • Self-invocation bypassing proxy-based retry behavior.
  • Retrying operations that are not idempotent.
  • Missing logs and metrics for retry outcomes.

Summary

  • Design retry and transaction boundaries as one unit.
  • Prefer fresh transaction per retry attempt.
  • Configure rollback rules to match actual exception types.
  • Ensure retried operations are idempotent.
  • Validate behavior with integration tests and production telemetry.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.