Spring Boot
Testing
Transactions
Rollback
JUnit

Transactions in spring boot testing not rolled back

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

Spring Boot tests often rely on transactional rollback to keep the database clean between runs. When rollback does not happen, test isolation breaks and failures become flaky. The issue usually comes from transaction boundaries, wrong test annotations, or code paths that commit outside the test transaction.

How Test Rollback Works

In Spring tests, rollback occurs when the test method runs inside a managed transaction and completes. The common pattern is using @Transactional on the test class or method.

java
1import org.junit.jupiter.api.Test;
2import org.springframework.boot.test.context.SpringBootTest;
3import org.springframework.transaction.annotation.Transactional;
4
5@SpringBootTest
6@Transactional
7class AccountServiceTest {
8
9    @Test
10    void testCreateAccount() {
11        // database writes inside this test should roll back automatically
12    }
13}

If writes happen in another transaction scope, rollback may not apply.

Common Causes of Missing Rollback

Typical root causes include:

  • Using @Commit or explicit transaction commit in test.
  • Running code in separate thread where test transaction context is absent.
  • Service methods annotated with REQUIRES_NEW creating independent commits.
  • Using non-transactional test slices with manual data setup.

Check service propagation settings first when behavior is surprising.

java
1import org.springframework.transaction.annotation.Propagation;
2import org.springframework.transaction.annotation.Transactional;
3
4@Transactional(propagation = Propagation.REQUIRES_NEW)
5public void saveAuditRecord() {
6    // this commit is independent from outer test transaction
7}

Independent transactions are valid in production but may leave rows after tests.

Keep Tests Deterministic

A pragmatic strategy is to avoid REQUIRES_NEW paths in most integration tests unless they are the explicit subject of the test. For those cases, add cleanup steps or use test containers reset.

java
1import org.junit.jupiter.api.AfterEach;
2import org.springframework.beans.factory.annotation.Autowired;
3
4class CleanupSupport {
5    @Autowired
6    AccountRepository repo;
7
8    @AfterEach
9    void cleanup() {
10        repo.deleteAll();
11    }
12}

Manual cleanup is slower but reliable for edge transaction cases.

Verify Transaction Context in Tests

You can assert active transaction state during test execution.

java
1import org.springframework.transaction.support.TransactionSynchronizationManager;
2
3boolean active = TransactionSynchronizationManager.isActualTransactionActive();
4System.out.println("transaction active: " + active);

This quick check confirms whether rollback can even be expected.

Configuration Checks

Ensure test points to the intended database and profile. Rollback confusion sometimes comes from writing to a different datasource than the one you inspect.

yaml
1spring:
2  datasource:
3    url: jdbc:h2:mem:testdb
4  jpa:
5    hibernate:
6      ddl-auto: create-drop

Consistent test configuration reduces hidden state leakage.

Async and Event-Driven Side Effects

Rollback does not automatically undo effects triggered through async executors, messaging systems, or external APIs. If test code publishes events that process in separate transactions, database state can persist.

java
1import org.springframework.scheduling.annotation.Async;
2import org.springframework.stereotype.Service;
3
4@Service
5class AsyncWriter {
6    @Async
7    public void writeAsync() {
8        // runs outside test transaction context
9    }
10}

For deterministic tests, disable async behavior or replace with synchronous test doubles.

Transactional Test Utilities

Use TestTransaction when you need explicit control over transaction boundaries in tests.

java
1import org.springframework.test.context.transaction.TestTransaction;
2
3// inside a transactional test
4System.out.println(TestTransaction.isActive());
5TestTransaction.flagForRollback();
6TestTransaction.end();

This helps debug where commits happen and why rollback did not occur.

Test Slice Differences

Different Spring test slices provide different transaction semantics. For example, data-layer slices often default to rollback behavior, while full integration tests depend on explicit annotations.

java
// @DataJpaTest usually includes transactional behavior by default
// @SpringBootTest may require explicit @Transactional on test class

Knowing slice behavior avoids false assumptions about automatic cleanup.

Practical Stabilization Checklist

A practical checklist for flaky rollback tests:

  • Confirm active transaction in test thread.
  • Inspect propagation settings on called services.
  • Disable async paths in test profile.
  • Add explicit cleanup for non-transactional side effects.

This approach stabilizes suites without guessing.

Common Pitfalls

  • Assuming rollback applies across threads or async executors.
  • Ignoring REQUIRES_NEW transactional boundaries in service methods.
  • Combining @Transactional tests with explicit commit annotations.
  • Inspecting a different database than the one used by tests.
  • Relying on rollback for tests that intentionally commit side effects.

Summary

  • Rollback works only inside the managed test transaction scope.
  • Independent transaction propagation can bypass test rollback.
  • Verify transaction activity during tests when debugging.
  • Use cleanup hooks for scenarios that must commit independently.
  • Keep datasource and profile configuration explicit and test-specific.

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.