Course topics

By WebNest Studio Engineering Team

Spring Framework Tutorial

Transaction Management with @Transactional

Many operations need to succeed or fail as a single unit — transferring money between two accounts, for example, must either update both balances or update neither. Spring's declarative transaction management lets you express this requirement with a single annotation, @Transactional, instead of manually managing commit and rollback calls around your database code.

Under the hood, Spring wraps an annotated method (or every method of an annotated class) in a proxy that starts a transaction before the method runs, commits it if the method completes normally, and rolls it back if the method throws a runtime exception. This declarative approach keeps transaction boundaries visible and consistent without scattering try-catch-rollback logic throughout the codebase.

How @Transactional Works

Placing @Transactional on a method (most commonly a service-layer method that calls one or more repository methods) tells Spring's proxy to begin a transaction just before the method executes, and to commit that transaction once the method returns successfully. If the method throws an unchecked (RuntimeException or Error) exception, the proxy rolls the transaction back automatically. By default, checked exceptions do NOT trigger a rollback unless explicitly configured to.

Propagation Basics

Propagation controls how a transactional method behaves when it is called from within another method that is already running inside a transaction.

  • REQUIRED (default) — joins the existing transaction if one is active, or starts a new one if not.
  • REQUIRES_NEW — always suspends any existing transaction and starts a brand-new, independent one.
  • SUPPORTS — joins an existing transaction if present, but runs non-transactionally if none exists.
  • MANDATORY — requires an existing transaction to already be active, and throws an exception if none is found.

Controlling Rollback Behavior

The default rollback rule (unchecked exceptions trigger rollback, checked exceptions do not) can be overridden with rollbackFor and noRollbackFor attributes on @Transactional, e.g. @Transactional(rollbackFor = Exception.class) to force a rollback even for checked exceptions. Other useful attributes include readOnly = true, which hints to the underlying persistence provider that no writes will occur (enabling potential optimizations), and timeout, which aborts the transaction if it runs too long.

Example

A money-transfer method that rolls back both updates if either fails

Java
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Repository
class AccountRepository {
    void debit(String accountId, double amount) {
        System.out.println("Debited " + amount + " from " + accountId);
    }
    void credit(String accountId, double amount) {
        System.out.println("Credited " + amount + " to " + accountId);
        if (accountId.equals("ACC-BAD")) {
            throw new RuntimeException("Account frozen, cannot credit");
        }
    }
}

@Service
class TransferService {
    private final AccountRepository accountRepository;
    TransferService(AccountRepository accountRepository) { this.accountRepository = accountRepository; }

    @Transactional
    public void transfer(String fromAccount, String toAccount, double amount) {
        accountRepository.debit(fromAccount, amount);
        accountRepository.credit(toAccount, amount); // throws for ACC-BAD
        // If credit() throws, Spring's proxy rolls back the whole transaction,
        // so the debit() above is also undone at the database level.
    }
}
// transferService.transfer("ACC-100", "ACC-BAD", 500.0)
Output
Debited 500.0 from ACC-100
Credited 500.0 to ACC-BAD
RuntimeException: Account frozen, cannot credit
(Transaction rolled back — the debit from ACC-100 is undone in the database)

Common Mistakes

  • Assuming @Transactional rolls back on any exception — by default only unchecked exceptions (RuntimeException/Error) trigger rollback; checked exceptions require rollbackFor to be set explicitly.
  • Calling a @Transactional method from another method in the same class (self-invocation) and expecting the transaction to start — like other Spring proxy-based features, this bypasses the proxy entirely.
  • Marking a read-heavy method readOnly = true but then performing a write inside it, leading to inconsistent or provider-dependent behavior.
  • Overusing REQUIRES_NEW without understanding it suspends the outer transaction, which can create partial commits if the outer transaction later fails.

Exercise

Using the lesson's A money-transfer method that rolls back both updates if either fails example, create a small implementation or design sketch that demonstrates @Transactional wraps a method in a proxy that begins a transaction before it runs and commits or rolls back based on the outcome.. Add a test case or scenario that would catch Assuming @Transactional rolls back on any exception — by default only unchecked exceptions (RuntimeException/Error) trigger rollback; checked exceptions require rollbackFor to be set explicitly., and describe the expected result.

Show hints
  • Start with How @Transactional Works and compare your approach with A money-transfer method that rolls back both updates if either fails. Identify the decision or behavior the lesson recommends.
  • Walk through your scenario step by step. Confirm that it follows @Transactional wraps a method in a proxy that begins a transaction before it runs and commits or rolls back based on the outcome. and that it would catch the failure described by Assuming @Transactional rolls back on any exception — by default only unchecked exceptions (RuntimeException/Error) trigger rollback; checked exceptions require rollbackFor to be set explicitly..

Quick quiz

1. Which choice is a mistake to avoid in Transaction Management with @Transactional?

  • A. @Transactional wraps a method in a proxy that begins a transaction before it runs and commits or rolls back based on the outcome.
  • B. Assuming @Transactional rolls back on any exception — by default only unchecked exceptions (RuntimeException/Error) trigger rollback; checked exceptions require rollbackFor to be set explicitly.
  • C. Calling a @Transactional method from another method in the same class (self-invocation) and expecting the transaction to start — like other Spring proxy-based features, this bypasses the proxy entirely.
Show answer

B. Avoid this approach: Assuming @Transactional rolls back on any exception — by default only unchecked exceptions (RuntimeException/Error) trigger rollback; checked exceptions require rollbackFor to be set explicitly. The lesson recommends: @Transactional wraps a method in a proxy that begins a transaction before it runs and commits or rolls back based on the outcome.

Interview questions

How would you apply the guidance in How @Transactional Works to a real task, and what failure would you guard against?

Show answer

@Transactional wraps a method in a proxy that begins a transaction before it runs and commits or rolls back based on the outcome. Apply this in How @Transactional Works; the example A money-transfer method that rolls back both updates if either fails shows one way to do it. Avoid Assuming @Transactional rolls back on any exception — by default only unchecked exceptions (RuntimeException/Error) trigger rollback; checked exceptions require rollbackFor to be set explicitly. and explain how your implementation or design prevents that failure.

Key Points to Remember

  • @Transactional wraps a method in a proxy that begins a transaction before it runs and commits or rolls back based on the outcome.
  • By default, only unchecked exceptions trigger an automatic rollback; use rollbackFor to include checked exceptions.
  • REQUIRED (default) joins an existing transaction or starts one; REQUIRES_NEW always starts an independent transaction.
  • Self-invocation within the same class bypasses the transactional proxy entirely, just like with Spring AOP in general.

Practice in the playground

Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.

Run the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

These examples need a JDK project with their libraries on your computer, so they can be copied but not run here. Codelab runs Python, FastAPI backends with SQLite, plain Java 17 and HTML/CSS/JavaScript.

WebNest Studio

Written by

WebNest Studio Engineering Team

Practical lessons from developers working with Java, Python, React, databases and production systems.

Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering