skip to content

Isolation Levels

Isolation levels trade concurrency against dirty, non-repeatable and phantom reads, and databases support them unevenly. Interviewers ask you to match an anomaly to the level that prevents it, then to say which level your database uses by default.

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

questions

5

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

level: juniorimportance: must knowfreq 62%

answer

  1. I in ACID — the visibility knob
  2. Isolation enum: DEFAULT + 4 real levels
  3. Maps to JDBC Connection.setTransactionIsolation
  4. Higher = more consistent, less concurrent
  5. DEFAULT = let the DB decide

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.

solid answer

~40 s

`isolation` controls how visible concurrent transactions' uncommitted or committed changes are to the current transaction — the classic trade-off between consistency and concurrency. Spring's `org.springframework.transaction.annotation.Isolation` enum has five values: `DEFAULT` (use the database's own default), `READ_UNCOMMITTED`, `READ_COMMITTED`, `REPEATABLE_READ`, and `SERIALIZABLE`. Spring maps these onto the JDBC `Connection` isolation constants; the transaction manager applies the level to the physical connection when the transaction starts. Higher levels progressively prevent read anomalies (dirty, non-repeatable, phantom reads) but cost throughput through more locking or aborts. `DEFAULT` is by far the most common in real apps — you let the DB decide (often READ_COMMITTED on Postgres/Oracle, REPEATABLE_READ on MySQL InnoDB) and only override when a specific consistency requirement demands it.

code

java · 16 lines
java
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class AccountService {

    // Let the database pick its own default level (the common case).
    @Transactional
    public Account load(long id) { /* ... */ }

    // Override only where a stronger guarantee is required.
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void transfer(long from, long to, BigDecimal amount) {
        // ...
    }
}

go deeper

for a junior

Must name the five enum values and state the consistency-vs-concurrency trade-off.

for a middle

Should know it maps to JDBC connection isolation and that DEFAULT defers to the DB.

for a senior

Explains when Spring physically applies and resets the level, and why DEFAULT is the pragmatic norm.

for a principal

Frames isolation as a per-operation tuning decision balanced against lock contention and DB engine behavior.

## What isolation means When many transactions run at once, one transaction can accidentally see intermediate or changing data produced by another. **Isolation level** is the knob that decides how much a transaction is protected from those concurrent effects. It is one of the four ACID properties (the *I*). ## The Spring attribute You set it declaratively: ```java @Transactional(isolation = Isolation.REPEATABLE_READ) ``` The enum is `org.springframework.transaction.annotation.Isolation` with five constants: | Spring value | JDBC constant | Meaning | |---|---|---| | `DEFAULT` | (-1) | Don't override — use the datastore's own default level | | `READ_UNCOMMITTED` | `TRANSACTION_READ_UNCOMMITTED` (1) | Weakest; can see uncommitted data | | `READ_COMMITTED` | `TRANSACTION_READ_COMMITTED` (2) | Only see committed data | | `REPEATABLE_READ` | `TRANSACTION_REPEATABLE_READ` (4) | Re-reading a row gives the same value | | `SERIALIZABLE` | `TRANSACTION_SERIALIZABLE` (8) | Strongest; transactions behave as if run one-at-a-time | ## How Spring applies it When the transaction manager (`DataSourceTransactionManager`, `JpaTransactionManager`, etc.) begins a **new physical transaction**, it takes the JDBC `Connection` and calls `connection.setTransactionIsolation(level)` (unless the value is `DEFAULT`). After the transaction ends, Spring resets the connection to its prior level before returning it to the pool. Because the level lives on the connection, it is set **once when the transaction/connection starts** — you cannot meaningfully change it partway through. ## The consistency vs. concurrency trade-off Going up the levels prevents more anomalies but reduces parallelism: databases achieve higher levels with more/longer locks or with snapshot-and-abort schemes, so you get more blocking, more serialization failures, or lower throughput. That is why `DEFAULT` (letting the DB pick, usually READ_COMMITTED-ish) is the pragmatic norm, and you raise it only for the specific method that needs stronger guarantees. ## Gotchas - `DEFAULT` does **not** mean READ_COMMITTED everywhere — MySQL InnoDB defaults to REPEATABLE_READ, Oracle/Postgres to READ_COMMITTED. - Not every database supports every level (Oracle has no READ_UNCOMMITTED or REPEATABLE_READ). - The level only takes effect where a real transaction/connection is created — it is ignored when a method merely *joins* an already-running transaction (see propagation).

  • What does `Isolation.DEFAULT` actually resolve to?
    It tells Spring not to override anything, so the effective level is whatever the underlying database defines as its default — e.g. READ_COMMITTED on PostgreSQL/Oracle, REPEATABLE_READ on MySQL InnoDB. It is database-specific, not a fixed level.
  • Where in the lifecycle is the isolation level applied?
    When the transaction manager begins a new physical transaction it calls `Connection.setTransactionIsolation(...)` on the JDBC connection, and restores the original level when the transaction completes and the connection returns to the pool.

saying these in an interview costs you the question

  • Saying DEFAULT always equals READ_COMMITTED
  • Thinking isolation controls whether a method runs in a transaction at all (that's propagation)
  • Believing you can change the level in the middle of a transaction

context

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

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

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