skip to content

Declarative @Transactional

The @Transactional attributes you actually set: enabling the interceptor, isolation, readOnly, timeout, and the rollback rules. These details are how interviewers distinguish annotation habits from understanding.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

explore

questions

25

What does @Transactional do, and what happens around a method annotated with it?

level: juniorimportance: must knowfreq 90%

answer

  1. proxy wraps bean, TransactionInterceptor
  2. begin -> run -> commit / rollback
  3. rollback on unchecked only, not checked
  4. class-level default, method-level overrides
  5. declarative, no manual begin/commit

basics

~10 s

@Transactional tells Spring to run the method inside a database transaction: Spring starts a transaction before the method, commits if it returns normally, and rolls back if it throws a runtime exception.

solid answer

~40 s

@Transactional is declarative transaction management: instead of writing begin/commit/rollback by hand, you annotate a class or method and Spring wraps it in a proxy. When a call crosses that proxy, Spring opens a transaction (or joins an existing one, per the propagation setting), runs your code, then commits on normal return. It rolls back automatically on unchecked exceptions (RuntimeException, Error) but NOT on checked exceptions unless you configure rollbackFor. It works through Spring AOP, so a TransactionInterceptor sits around the target bean and delegates the actual commit/rollback to a configured TransactionManager (e.g. JpaTransactionManager or DataSourceTransactionManager). Method-level annotations override class-level ones. You can tune propagation, isolation, readOnly, timeout, and rollback rules as attributes.

code

java · 20 lines
java
@Service
public class TransferService {

    private final AccountRepository accounts;

    public TransferService(AccountRepository accounts) {
        this.accounts = accounts;
    }

    // One atomic unit: both saves commit together, or neither does.
    @Transactional
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        Account from = accounts.findById(fromId).orElseThrow();
        Account to   = accounts.findById(toId).orElseThrow();
        from.debit(amount);   // throws if insufficient funds -> RuntimeException -> rollback
        to.credit(amount);
        accounts.save(from);
        accounts.save(to);
    }
}

go deeper

for a junior

Know that it wraps the method in a DB transaction and commits/rolls back automatically.

for a middle

Know the default rollback rule (unchecked only), class vs method placement, and the proxy mechanism.

for a senior

Be able to name the TransactionInterceptor/TransactionManager path and reason about propagation and readOnly.

for a principal

Frame it as declarative AOP-based cross-cutting concern and discuss when programmatic (TransactionTemplate) or reactive transaction management is a better fit.

## What a transaction is A database **transaction** is a unit of work that is atomic: either all its statements commit together or they are all rolled back. Without transactions, a failure halfway through a multi-step operation (e.g. debit one account, credit another) can leave data half-updated. ## Declarative vs programmatic You could manage this by hand (`connection.setAutoCommit(false)`, `commit()`, `rollback()`) — that is *programmatic* transaction management (`TransactionTemplate` is Spring's helper). `@Transactional` is *declarative*: you annotate a bean method and Spring adds the begin/commit/rollback around it for you. Less boilerplate, no transaction plumbing mixed into business logic. ## How it works mechanically `@Transactional` relies on **Spring AOP proxies**. When Spring detects transactional beans, it wraps each one in a proxy (a JDK dynamic proxy if the bean implements an interface, otherwise a CGLIB subclass proxy). The proxy holds a `TransactionInterceptor`. When an external caller invokes an annotated method: 1. The interceptor reads the method's `TransactionAttribute` (from `AnnotationTransactionAttributeSource`). 2. It asks the configured `TransactionManager` (a `PlatformTransactionManager` such as `JpaTransactionManager`, `DataSourceTransactionManager`, or `JtaTransactionManager`) to start or join a transaction per the **propagation** rule. 3. Your method body runs. 4. On normal return it commits; on a matching exception it rolls back; then it restores the previous transaction context. ## Default rollback rule (critical gotcha) Spring rolls back automatically only on **unchecked** exceptions — `RuntimeException` and `Error`. A **checked** exception (e.g. `IOException`) does NOT trigger rollback by default; the transaction commits. To change this: `@Transactional(rollbackFor = Exception.class)`. Conversely, `noRollbackFor` suppresses rollback for chosen runtime exceptions. ## Placement - On a **class**: applies to all public methods as a default. - On a **method**: overrides the class-level setting for that method. - Method-level always wins over class-level. ## Common attributes - `propagation` — REQUIRED (default: join or start), REQUIRES_NEW (suspend and start fresh), SUPPORTS, MANDATORY, NEVER, NOT_SUPPORTED, NESTED. - `isolation` — DEFAULT, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE. - `readOnly` — a hint; lets JPA/Hibernate skip dirty-checking and drivers optimize. - `timeout` — seconds before the transaction is rolled back. - `rollbackFor` / `noRollbackFor` — override the default rollback rule. ## When to use Any service method that performs multiple writes that must succeed or fail together. Put it on the **service layer**, not repositories or controllers, so one business operation equals one transaction.

  • Does @Transactional roll back on a checked exception by default?
    No. By default Spring rolls back only on RuntimeException and Error. A checked exception commits unless you add rollbackFor = Exception.class (or a more specific checked type).
  • Which layer should carry @Transactional, and why?
    The service layer, because a business operation (often spanning several repository calls) should be one transactional unit. Putting it on repositories fragments a single operation into many transactions.

saying these in an interview costs you the question

  • Claiming @Transactional rolls back on any exception, including checked ones
  • Thinking it runs the method on a background thread or asynchronously
  • Saying it uses reflection to rewrite the method rather than an AOP proxy

context

open as a page

What does the `isolation` attribute of `@Transactional` control, and what are the five values Spring offers?

level: juniorimportance: must knowfreq 62%

basics

~10 s

It sets how much one transaction is shielded from data other in-flight transactions are changing. Spring's Isolation enum offers DEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, and SERIALIZABLE — higher levels give more consistency but less concurrency.

open as a page

What does @Transactional(readOnly = true) do in Spring?

level: juniorimportance: must knowfreq 70%

basics

~20 s

It marks a transaction as read-only: a hint that the method only reads data. Spring passes this to Hibernate and the JDBC driver so they can optimize, for example by skipping dirty-checking and the automatic flush.

open as a page

By default, which exceptions cause a Spring @Transactional method to roll back, and which let it commit?

level: juniorimportance: must knowfreq 80%

basics

~10 s

By default Spring rolls back on unchecked exceptions (RuntimeException) and Error. It commits on checked exceptions (any Exception that isn't a RuntimeException). You must opt in to roll back on checked exceptions.

open as a page

What does the timeout attribute of @Transactional do, and in what unit do you specify it?

level: juniorimportance: must knowfreq 40%

basics

~10 s

@Transactional(timeout = N) limits how long the transaction may run to N seconds. If it exceeds that, Spring aborts it and rolls back, throwing a TransactionTimedOutException. The unit is always seconds.

open as a page

Explain dirty read, non-repeatable read, and phantom read, and which isolation level prevents each.

level: middleimportance: must knowfreq 70%

basics

~20 s

Dirty read = seeing another transaction's uncommitted data. Non-repeatable read = re-reading a row and getting a changed value. Phantom read = re-running a query and getting new rows. READ_COMMITTED stops dirty, REPEATABLE_READ stops non-repeatable, SERIALIZABLE stops phantoms.

open as a page

Does @Transactional(readOnly = true) prevent writes? What happens if you modify an entity inside one?

level: middleimportance: must knowfreq 65%

basics

~20 s

Usually nothing is written, but not because writes are forbidden. Because FlushMode is MANUAL, Hibernate never auto-flushes, so a dirty entity change is silently discarded at commit. It is not an error — it is a missing flush.

open as a page

How do rollbackFor and noRollbackFor change the default rollback behavior, and when would you use each?

level: middleimportance: must knowfreq 72%

basics

~20 s

rollbackFor adds exception types (including checked ones) that should trigger rollback. noRollbackFor names exceptions that should NOT roll back even though they normally would (e.g. a RuntimeException you treat as a business outcome). Both go on @Transactional.

open as a page

Why does @Transactional only work on public methods in proxy mode, and why is a self-invocation ignored?

level: seniorimportance: must knowfreq 75%

basics

~20 s

In the default proxy mode, the transaction lives in a proxy that wraps the bean. The proxy can only intercept public methods, and only calls coming from outside the object. A method calling another method on 'this' skips the proxy, so @Transactional is ignored.

open as a page

What does @EnableTransactionManagement do, and do you need it in a Spring Boot app?

level: middleimportance: should knowfreq 60%

basics

~20 s

@EnableTransactionManagement turns on annotation-driven transactions by registering the AOP infrastructure that makes @Transactional work. In Spring Boot you usually do not add it — auto-configuration enables it for you when a transaction manager is present.

open as a page

Under the hood, how does Spring actually enforce the transaction timeout? Trace it from TransactionDefinition to the JDBC layer.

level: middleimportance: should knowfreq 32%

basics

~20 s

When the transaction starts, Spring turns the N-second timeout into a deadline stored on the resource holder (e.g. ConnectionHolder). Before each JDBC statement runs, Spring applies the remaining time via Statement.setQueryTimeout(), and if the deadline has already passed it throws TransactionTimedOutException.

open as a page

Walk through how a call to a @Transactional method flows through the TransactionInterceptor and advisor at runtime.

level: seniorimportance: should knowfreq 45%

basics

~20 s

The proxy hands the call to the TransactionInterceptor. It reads the method's transaction settings, asks the TransactionManager to start or join a transaction, runs the real method, then commits on success or rolls back on a matching exception, and cleans up.

open as a page

What database-specific caveats must you know before relying on a particular `isolation` level in Spring?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Spring just forwards the level to the driver; the DB decides what it means. Not all levels are supported (Oracle has only READ_COMMITTED and SERIALIZABLE), default levels differ (MySQL=REPEATABLE_READ, Postgres/Oracle=READ_COMMITTED), and some engines silently map or strengthen levels.

open as a page

What happens to the `isolation` attribute when a `@Transactional` method joins an already-running transaction?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Isolation is applied only when a new physical transaction starts. If the method uses the default PROPAGATION_REQUIRED and joins an existing transaction, its declared isolation is ignored — the outer transaction's level wins. You can make Spring throw instead by enabling validateExistingTransaction.

open as a page

What does readOnly=true mean at the JDBC/connection level, and how is it used for replica routing?

level: seniorimportance: should knowfreq 40%

basics

~20 s

At the JDBC level Spring may call Connection.setReadOnly(true). That is a driver hint some databases optimize and, more usefully, apps use it to route the connection to a read replica while write transactions go to the primary.

open as a page

How does readOnly=true interact with Hibernate FlushMode and dirty checking?

level: seniorimportance: should knowfreq 45%

basics

~10 s

Spring's Hibernate integration sets the Session FlushMode to MANUAL when readOnly=true. That disables the automatic flush before queries and at commit, so Hibernate skips dirty-checking of managed entities, saving the snapshot-and-compare work.

open as a page

How does RuleBasedTransactionAttribute decide commit vs rollback when multiple rollbackFor/noRollbackFor rules could match the thrown exception?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Spring stores the rules in a RuleBasedTransactionAttribute. For a thrown exception it picks the rule whose type is the CLOSEST match in the exception's class hierarchy (smallest depth). If that winning rule is a noRollbackFor rule it commits; if rollbackFor, it rolls back. If no rule matches, it uses the default (rollback on RuntimeException/Error).

open as a page

Why might a @Transactional(timeout = ...) value appear to be ignored at runtime? Cover propagation and what the timeout does and doesn't bound.

level: seniorimportance: should knowfreq 28%

basics

~20 s

The timeout only applies to the transaction Spring actually starts. If your method joins an existing (PROPAGATION_REQUIRED) transaction, your timeout is silently ignored by default. Also, timeout is enforced only at database-statement boundaries, so pure CPU/sleep work between queries isn't interrupted mid-flight.

open as a page

Tell me about TransactionTimedOutException: what type is it, when is it thrown, and how does it differ from a database lock timeout or a JDBC query timeout?

level: seniorimportance: should knowfreq 22%

basics

~20 s

TransactionTimedOutException is Spring's unchecked exception (a TransactionException) thrown when a transaction's timeout deadline has passed and Spring tries to use the resource again. It forces a rollback. It's distinct from a DB lock timeout or a JDBC query timeout, which are database-level errors surfaced as SQLExceptions.

open as a page

What are the propagation and enforcement pitfalls of @Transactional(readOnly = true)? When does the flag actually take effect?

level: principalimportance: should knowfreq 30%

basics

~20 s

readOnly is set when a physical transaction begins. If an inner method joins an existing (REQUIRED) transaction, its own readOnly value is ignored — the outer transaction's flag wins. And because it is only a hint, real no-write guarantees must come from the DB, not this flag.

open as a page

You're setting a project-wide policy for transaction rollback rules. What trade-offs, pitfalls, and interactions with propagation would you weigh?

level: principalimportance: should knowfreq 30%

basics

~20 s

Decide whether all exceptions should roll back (rollbackFor = Exception.class) or keep the default. Watch for: swallowed exceptions bypassing rules, self-invocation not being proxied, and inner rollbacks marking the whole physical transaction rollback-only, causing UnexpectedRollbackException in outer methods.

open as a page

What are the semantics and risks of rollbackForClassName / noRollbackForClassName string matching?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

They match by exception class-name substring, not exact type. Spring walks up the exception's superclasses and matches if a class name CONTAINS the given string. That makes short patterns like "Exception" match almost everything, so use specific patterns.

open as a page

When would you choose AdviceMode.ASPECTJ over the default proxy mode for @EnableTransactionManagement, and what are the trade-offs?

level: principalimportance: nice to knowfreq 25%

basics

~20 s

Choose ASPECTJ when you need transactions on non-public methods or on self-invoked internal calls, which proxy mode can't advise. The cost is a weaving setup (compile-time or load-time) and more complex builds; proxy mode is simpler and the default.

open as a page

As an architect, how do you decide when to raise the isolation level versus using other concurrency-control tools?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Default to the DB's level and solve specific anomalies with targeted tools: optimistic locking (JPA @Version) for rare conflicts, pessimistic locks (SELECT ... FOR UPDATE) for hot rows. Reserve SERIALIZABLE for genuine serialization needs, and always plan retries.

open as a page

You want reliable time bounds on database work in a high-throughput service. Where does Spring's @Transactional timeout fall short, and how would you design defense-in-depth around it?

level: principalimportance: nice to knowfreq 12%

basics

~20 s

Spring's timeout depends on the JDBC driver honoring setQueryTimeout and is only checked at statement boundaries, so it won't catch driver no-ops or long non-DB work. Layer it with DB-side statement_timeout and lock_timeout, keep external calls out of transactions, and set connection-pool timeouts.

open as a page