How do you make a transactional test commit its changes instead of rolling back, and when would you?
answer
- @Commit == @Rollback(false)
- Method-level overrides class-level
- Committing forfeits isolation -> add cleanup
- AFTER_COMMIT events need a real commit
- Both from org.springframework.test.annotation
basics
~20 sAdd @Commit (or equivalently @Rollback(false)) on the test method or class. Then Spring commits the transaction instead of rolling it back. Useful rarely — e.g. inspecting persisted data or seeding across tests — but it breaks isolation, so you must clean up yourself.
solid answer
~40 sBy default the TransactionalTestExecutionListener rolls back. To commit, annotate the test method or class with @Commit, which is exactly shorthand for @Rollback(false) — both live in org.springframework.test.annotation. Method-level wins over class-level, so you can set a class default and override per method. You'd commit when you genuinely need the data to persist: debugging what got written, verifying behavior across separate transactions, or seeding data other tests depend on. But committing throws away the free isolation and cleanup, so you must add your own teardown (@AfterEach / @Sql cleanup scripts) or tests will leak state and become order-dependent. In practice most teams keep rollback and reach for @Commit only in narrow cases. @Rollback(true) can also force rollback where a class set @Commit.
code
java · 18 lines@DataJpaTest
class AuditTrailTest {
@Autowired AuditRepository audit;
@Test
@Commit // equivalently @Rollback(false): persist so an AFTER_COMMIT listener can fire
void commitTriggersAfterCommitListener() {
audit.save(new AuditEntry("login"));
// Because this commits, an @TransactionalEventListener(phase = AFTER_COMMIT)
// will actually run — which it never would under the default rollback.
}
@AfterEach
void cleanup() {
audit.deleteAll(); // MANDATORY once you commit: restore isolation yourself
}
}go deeper
Knows @Commit / @Rollback(false) flips the default to commit.
Knows the equivalence, method-over-class precedence, and that committing means you must clean up.
Cites isRollback resolution, AFTER_COMMIT-listener use case, and the isolation trade-off.
Weighs committing vs TestTransaction vs Testcontainers per-test schema for verifying commit-boundary behavior at suite scale.
## The annotations Both from `org.springframework.test.annotation`: - **`@Rollback`** — takes a boolean. `@Rollback(true)` (default) rolls back; `@Rollback(false)` commits. - **`@Commit`** — a semantic alias meaning "commit this test transaction." It is **exactly equivalent to `@Rollback(false)`**. It exists purely for readability. Both can be applied at **class level** (sets the default for all methods) or **method level** (overrides the class default). Method-level always wins. ```java @SpringBootTest @Transactional @Commit // class default: commit everything class MigrationSmokeTest { @Test void seedsReferenceData() { /* commits */ } @Test @Rollback(true) // override: this one rolls back void temporaryProbe() { /* rolls back */ } } ``` ## How the framework decides `TransactionalTestExecutionListener.isRollback(...)` looks for `@Rollback`/`@Commit` on the method first, then the class, defaulting to `true` (rollback). The result determines whether the managed transaction is committed or rolled back after the method. ## When to commit (legitimately) - **Debugging**: you want to open the DB afterward and see exactly what the code wrote. - **Cross-transaction verification**: the behavior under test spans commit boundaries (e.g. an event fired on commit, an `@TransactionalEventListener(phase = AFTER_COMMIT)`), which a rolled-back test can never trigger. (Programmatic `TestTransaction` is often a cleaner tool here.) - **Ordered seed steps** in a controlled suite. ## The cost of committing Committing **destroys the isolation guarantee**. You now own cleanup: - Add `@AfterEach` delete logic, or - Use `@Sql(scripts = "cleanup.sql", executionPhase = AFTER_TEST_METHOD)`, or - Use `@DirtiesContext` / rebuild schema between tests (heavy). Forgetting cleanup makes tests **order-dependent and flaky** — the classic symptom of misusing `@Commit`. ## Gotchas - `@Commit` does not commit *mid-test*; it just changes the end-of-method decision. To commit and continue in a new transaction within one method, use `TestTransaction`. - `@Commit` only affects the **test-managed** transaction. If application code opens its own `REQUIRES_NEW` transaction, that commits independently regardless. - These annotations are inert without a test-managed transaction — i.e. the test (or slice) must also be `@Transactional`.
- Is @Commit different from @Rollback(false)?No — @Commit is a readable alias that means exactly @Rollback(false). Both cause the test-managed transaction to commit.
- You committed in a test but a later test now fails intermittently. Why?Committing leaves rows behind, so tests became order-dependent. You need explicit cleanup (@AfterEach, @Sql AFTER_TEST_METHOD) or you should revert to the default rollback.
saying these in an interview costs you the question
- Saying @Commit and @Rollback(false) are semantically different
- Believing @Commit commits mid-test rather than at method end
- Committing without adding any cleanup and expecting tests to stay isolated
- Thinking class-level @Commit cannot be overridden per method