August 15, 2026 • Java

11 - Spring Transactional Annotation

Table of Contents

  1. Introduction
  2. What is a Transaction?
  3. ACID Principles
  4. Transactional Context
  5. Transaction Managers
  6. Spring @Transactional
  7. Rollback Behavior
  8. Transaction Propagation
  9. Isolation Levels
  10. Transaction Timeouts
  11. Read-Only Transactions
  12. How Spring Implements Transactions
  13. Limitations of @Transactional
  14. Advantages and Disadvantages
  15. Best Practices
  16. Summary

Introduction

In enterprise applications, data consistency is critical.

Consider the following operations:

  • Money transfers
  • Order creation
  • Inventory updates
  • Payment processing

If an application crashes in the middle of these operations, data can become inconsistent.

To solve this problem, relational databases implement transactions, and Spring provides the @Transactional annotation to manage them automatically.


What is a Transaction?

A transaction is a logical unit of work that consists of one or more operations executed as a single indivisible unit.

A transaction has only two possible outcomes:

  • Commit → permanently save all changes.
  • Rollback → undo all changes.

Example

Bank transfer:

accountA.debit(100);
accountB.credit(100);

Both operations must succeed together.

If one operation fails:

Rollback Everything

ACID Principles

ACID is a set of properties that guarantee transaction reliability.

A — Atomicity

A transaction is all-or-nothing.

Example

accountA.debit(100);
accountB.credit(100);

If the second operation fails:

Rollback

Result:

No changes persisted

Benefit

Prevents partial updates.


C — Consistency

A transaction must move the database from one valid state to another valid state.

Example

Business rule:

balance >= 0

This operation is invalid:

account.setBalance(-500);

Result:

Transaction Rejected

Benefit

Maintains business rules and constraints.


I — Isolation

Concurrent transactions should not interfere with each other.

Example

Inventory quantity:

Stock = 1

Two users purchase simultaneously.

Without isolation:

Stock = -1

With proper isolation:

Stock = 0

Benefit

Prevents concurrency issues.


D — Durability

Once committed, data survives failures.

Examples:

  • Server restart
  • Power outage
  • Application crash
COMMIT;

After commit:

Data is permanently stored

Benefit

Guarantees persistence.


Transactional Context

What is a Transactional Context?

A transactional context is the execution scope in which all database operations participate in the same transaction.

When Spring starts a transaction:

Transaction Context Created

All repository operations executed inside that context share:

  • Same transaction
  • Same connection
  • Same commit
  • Same rollback

Example

@Service
public class UserService {

    @Transactional
    public void createUser() {

        userRepository.save(user);

        roleRepository.save(role);

        auditRepository.save(log);
    }
}

Execution:

Transaction Context
    ├── userRepository.save()
    ├── roleRepository.save()
    └── auditRepository.save()

If any operation fails:

Rollback Everything

Why is it Important?

Without a transactional context:

Operation 1 → Commit
Operation 2 → Fail
Operation 3 → Never Executes

Result:

Inconsistent Data

Transaction Managers

What is a Transaction Manager?

The Transaction Manager is responsible for:

  • Starting transactions
  • Committing transactions
  • Rolling back transactions

Spring delegates transaction management to a PlatformTransactionManager.


PlatformTransactionManager

Core Spring transaction interface:

public interface PlatformTransactionManager {
}

Spring automatically selects the appropriate implementation.


JpaTransactionManager

Used with:

  • JPA
  • Hibernate
  • Spring Data JPA
@Bean
public PlatformTransactionManager transactionManager(
        EntityManagerFactory emf) {

    return new JpaTransactionManager(emf);
}

DataSourceTransactionManager

Used with:

  • JDBC
  • JdbcTemplate
@Bean
public PlatformTransactionManager transactionManager(
        DataSource ds) {

    return new DataSourceTransactionManager(ds);
}

JtaTransactionManager

Used for distributed transactions.

Examples:

  • Multiple databases
  • Database + Messaging
  • XA transactions

Transaction Flow

@Transactional

Spring AOP Proxy

PlatformTransactionManager

Database

Spring @Transactional

What is @Transactional?

@Transactional is a Spring annotation that defines transactional boundaries.

Spring automatically:

  1. Opens a transaction.
  2. Executes the method.
  3. Commits on success.
  4. Rolls back on failure.

Example

@Service
public class TransferService {

    @Transactional
    public void transfer() {

        accountA.debit(100);

        accountB.credit(100);
    }
}

Execution:

Open Transaction

Debit

Credit

Commit

Failure:

Open Transaction

Debit

Exception

Rollback

Class-Level Transaction

@Service
@Transactional
public class OrderService {

    public void createOrder() {}

    public void cancelOrder() {}
}

All public methods become transactional.


Rollback Behavior

Default Rollback Rules

Spring rolls back automatically for:

RuntimeException
Error

Example:

@Transactional
public void process() {

    throw new RuntimeException();
}

Result:

Rollback

Checked Exceptions

Checked exceptions require explicit configuration.

@Transactional(
    rollbackFor = Exception.class
)
public void process() throws Exception {

    throw new Exception();
}

Result:

Rollback

Transaction Propagation

Propagation defines how a transactional method behaves when another transaction already exists.


REQUIRED (Default)

@Transactional(
    propagation = Propagation.REQUIRED
)

Behavior:

Existing Transaction?
    Yes → Join
    No → Create New

Most commonly used.


REQUIRES_NEW

@Transactional(
    propagation = Propagation.REQUIRES_NEW
)

Behavior:

Suspend Current Transaction
Create New Transaction
Execute
Commit
Resume Original Transaction

Common use cases:

  • Audit logs
  • Notifications
  • Event history

SUPPORTS

@Transactional(
    propagation = Propagation.SUPPORTS
)

Behavior:

Transaction Exists?
    Yes → Join
    No → Execute Without Transaction

Common for read operations.


NOT_SUPPORTED

@Transactional(
    propagation = Propagation.NOT_SUPPORTED
)

Behavior:

Suspend Existing Transaction
Execute Without Transaction
Resume Transaction

Useful for:

  • External API calls
  • Long-running tasks

MANDATORY

@Transactional(
    propagation = Propagation.MANDATORY
)

Requires an existing transaction.

Otherwise:

IllegalTransactionStateException

NEVER

@Transactional(
    propagation = Propagation.NEVER
)

Fails if a transaction exists.


NESTED

@Transactional(
    propagation = Propagation.NESTED
)

Creates savepoints.

Parent Transaction

      ├── Savepoint

      └── Nested Transaction

Allows partial rollback.

Database support varies.


Isolation Levels

Isolation levels determine how concurrent transactions interact.


Concurrency Problems

Dirty Read

Reading uncommitted data.

T1 Updates Value
T2 Reads Value
T1 Rollbacks

T2 read invalid data.


Non-Repeatable Read

Reading the same row twice returns different values.

T1 Reads Salary = 1000

T2 Updates Salary = 2000

T1 Reads Again = 2000

Phantom Read

A query returns additional rows.

SELECT * FROM employees

Another transaction inserts new rows.

Second query returns different results.


READ_UNCOMMITTED

Allows:

  • Dirty Reads
  • Non-repeatable Reads
  • Phantom Reads
@Transactional(
    isolation = Isolation.READ_UNCOMMITTED
)

Highest performance, lowest consistency.


READ_COMMITTED

Prevents:

  • Dirty Reads

Allows:

  • Non-repeatable Reads
  • Phantom Reads
@Transactional(
    isolation = Isolation.READ_COMMITTED
)

Most commonly used.


REPEATABLE_READ

Prevents:

  • Dirty Reads
  • Non-repeatable Reads

Allows:

  • Phantom Reads
@Transactional(
    isolation = Isolation.REPEATABLE_READ
)

Default in MySQL InnoDB.


SERIALIZABLE

Prevents:

  • Dirty Reads
  • Non-repeatable Reads
  • Phantom Reads
@Transactional(
    isolation = Isolation.SERIALIZABLE
)

Highest consistency.

Trade-offs:

  • More locks
  • Lower throughput

Isolation Comparison Table

Isolation LevelDirty ReadNon-Repeatable ReadPhantom Read
READ_UNCOMMITTED❌ Allowed❌ Allowed❌ Allowed
READ_COMMITTED✅ Prevented❌ Allowed❌ Allowed
REPEATABLE_READ✅ Prevented✅ Prevented❌ Allowed
SERIALIZABLE✅ Prevented✅ Prevented✅ Prevented

Transaction Timeouts

What is a Timeout?

Defines the maximum duration of a transaction.


Example

@Transactional(timeout = 10)
public void processOrder() {
}

Meaning:

Maximum Duration = 10 Seconds

If exceeded:

TransactionTimedOutException
Rollback

Why Use Timeouts?

Prevents:

  • Long-running transactions
  • Resource exhaustion
  • Lock contention
  • Deadlocks

Read-Only Transactions

What is a Read-Only Transaction?

Used when no data modifications are expected.

@Transactional(readOnly = true)
public User findUser(Long id) {
    return repository.findById(id).orElseThrow();
}

Benefits

Hibernate Optimization

Normal flow:

Load Entity

Track Changes

Flush

Read-only:

Load Entity

No Dirty Checking

Better Performance

Useful for:

@Transactional(readOnly = true)
public List<User> findAllUsers() {
    return repository.findAll();
}

Benefits:

  • Less memory
  • Less CPU
  • Faster execution

Important Note

Read-only does not always physically prevent writes.

Behavior depends on:

  • Database
  • JDBC Driver
  • Transaction Manager

How Spring Implements Transactions

Spring uses:

  • AOP (Aspect-Oriented Programming)
  • Dynamic Proxies

Internal Flow

Client

Spring Proxy

Open Transaction

Execute Method

Commit / Rollback

Equivalent logic:

beginTransaction();

try {
    businessMethod();
    commit();
}
catch(Exception ex) {
    rollback();
}

Limitations of @Transactional

Private Methods

Does not work.

@Transactional
private void save() {}

Reason:

Proxy Cannot Intercept Private Methods

Self Invocation

Does not work.

@Service
public class UserService {

    public void methodA() {
        methodB();
    }

    @Transactional
    public void methodB() {
    }
}

Reason:

Call Bypasses Spring Proxy

Advantages

Simplicity

@Transactional
public void save() {}

Less Boilerplate

No need for:

beginTransaction();
commit();
rollback();

Seamless Integration

Works with:

  • JPA
  • Hibernate
  • JDBC
  • Spring Data

Better Consistency

Helps enforce ACID principles.


Disadvantages

Long Transactions Can Be Dangerous

May cause:

  • Locks
  • Deadlocks
  • Performance degradation

External Calls Inside Transactions

Bad example:

@Transactional
public void process() {

    externalApi.call();

    repository.save(entity);
}

The transaction remains open during the external call.


Learning Curve

Developers must understand:

  • Propagation
  • Isolation
  • Rollback rules
  • Proxy behavior

Best Practices

✅ Place @Transactional in the Service layer.

✅ Keep transactions short.

✅ Use readOnly = true whenever possible.

✅ Avoid remote calls inside transactions.

✅ Understand propagation before using REQUIRES_NEW.

✅ Use timeouts for long-running processes.

❌ Do not place @Transactional on Controllers.

❌ Do not create large transactional scopes.

❌ Do not ignore isolation-level implications.


Summary

TopicPurpose
TransactionUnit of work
ACIDReliability guarantees
Transactional ContextShared transactional scope
Transaction ManagerControls transaction lifecycle
@TransactionalDeclarative transaction management
PropagationTransaction interaction rules
IsolationConcurrency control
TimeoutMaximum transaction duration
Read-OnlyQuery optimization
AOP ProxySpring transaction mechanism

Understanding these concepts is essential for building reliable, scalable, and consistent Spring applications. Together, ACID principles, transaction managers, propagation rules, isolation levels, and the @Transactional annotation form the foundation of transaction management in Spring Framework and Spring Boot.

← Back to blog