Chaturmind
LearnDSASystem DesignDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Spring Boot REST API Development

Spring Boot Basics

  • What is Spring Boot?
  • Dependency Injection
  • Building REST Controllers

Validation & Error Handling

  • Bean Validation with @Valid
  • Global Exception Handling

Spring Framework Internals

  • IoC Container Fundamentals
  • Bean Lifecycle In Detail
  • Component Scanning & Configuration
  • Auto-Configuration Mechanism
  • Spring AOP
  • @Transactional Deep Dive

Microservices Architecture

  • Monolith to Microservices Decomposition
  • API Gateway
  • Service Discovery
  • Inter-Service Communication Choices
  • Event-Driven Architecture Patterns
  • Messaging Technology Choices

Resilience Patterns

  • Configuration Management
  • Circuit Breaker Pattern
  • Retry & Backoff Strategies
  • Bulkhead & Rate Limiting
  • Timeout Strategy
  • Why Microservices Fail

Distributed Data & Consistency Patterns

  • Two-Phase Commit
  • Saga Pattern
  • Outbox Pattern
  • Eventual Consistency Design
  • CQRS Basics
  • CAP Theorem

Observability & Operations

  • Centralized Logging
  • Distributed Tracing
  • Metrics & Monitoring
  • Alerting Strategy
  • Health Checks

Payment Systems

  • Payment — Requirements
  • Payment — Core Flow
  • Payment — Idempotency Implementation
  • Payment — Failure Handling & Reconciliation
  • Payment — Security

Order Management System

  • OMS — Requirements
  • OMS — State Machine Design
Chaturmind
← Spring Boot REST API Development

Spring Boot Basics

  • What is Spring Boot?
  • Dependency Injection
  • Building REST Controllers

Validation & Error Handling

  • Bean Validation with @Valid
  • Global Exception Handling

Spring Framework Internals

  • IoC Container Fundamentals
  • Bean Lifecycle In Detail
  • Component Scanning & Configuration
  • Auto-Configuration Mechanism
  • Spring AOP
  • @Transactional Deep Dive

Microservices Architecture

  • Monolith to Microservices Decomposition
  • API Gateway
  • Service Discovery
  • Inter-Service Communication Choices
  • Event-Driven Architecture Patterns
  • Messaging Technology Choices

Resilience Patterns

  • Configuration Management
  • Circuit Breaker Pattern
  • Retry & Backoff Strategies
  • Bulkhead & Rate Limiting
  • Timeout Strategy
  • Why Microservices Fail

Distributed Data & Consistency Patterns

  • Two-Phase Commit
  • Saga Pattern
  • Outbox Pattern
  • Eventual Consistency Design
  • CQRS Basics
  • CAP Theorem

Observability & Operations

  • Centralized Logging
  • Distributed Tracing
  • Metrics & Monitoring
  • Alerting Strategy
  • Health Checks

Payment Systems

  • Payment — Requirements
  • Payment — Core Flow
  • Payment — Idempotency Implementation
  • Payment — Failure Handling & Reconciliation
  • Payment — Security

Order Management System

  • OMS — Requirements
  • OMS — State Machine Design
HomeLearnSpring BootSpring Boot REST API DevelopmentSpring Framework Internals
✓ FreeAdvanced· 9 min read

@Transactional Deep Dive

@Transactional is AOP advice, with the same self-invocation caveat as any other aspect — plus every propagation type, isolation override, rollback rule, and the readOnly hint's real effect.

Published September 23, 2026


@Transactional Deep Dive

It's AOP advice, not a language feature

As established in Spring AOP, @Transactional is implemented as an @Around-style aspect matching @annotation(Transactional) — meaning the exact same self-invocation caveat applies: calling a @Transactional method on this from within the same class bypasses the proxy entirely, and the transaction never actually begins. This is worth restating specifically for @Transactional, because it's the single most common place this bug bites people in practice — a method that "should" be transactional silently isn't, with no exception, no warning, just data written outside any transaction boundary.

Propagation: what happens when a transactional method calls another

@Transactional(propagation = Propagation.REQUIRED) // default
void outer() { inner(); } // if outer() is already in a transaction, inner() joins it

@Transactional(propagation = Propagation.REQUIRES_NEW)
void inner() { ... } // suspends any existing transaction, starts a completely independent new one
  • REQUIRED (default) — join the existing transaction if one is active; start a new one if not.
  • REQUIRES_NEW — always suspend any existing transaction and start a fresh, independent one — useful when a piece of work (e.g. an audit log write) must commit even if the outer transaction later rolls back.
  • NESTED — starts a true nested transaction with its own savepoint, rollback of which only undoes the nested portion, not the outer transaction (support varies by underlying datastore/driver — not universally available).
  • SUPPORTS — join a transaction if one exists; run without one otherwise (no independent behavior either way).
  • MANDATORY — must be called within an existing transaction; throws an exception if there isn't one.
  • NOT_SUPPORTED — suspends any existing transaction and runs without one.
  • NEVER — throws an exception if called within an existing transaction at all.

REQUIRED covers the overwhelming majority of real cases; REQUIRES_NEW is the one worth understanding deeply, since "this specific side effect needs to survive even if the caller rolls back" is a genuinely common real requirement.

Isolation override

@Transactional(isolation = Isolation.READ_COMMITTED)

Overrides the database's default isolation level for this specific transaction — useful when a particular operation needs stronger (or, more rarely, weaker) consistency guarantees than the rest of the application's default, without changing the database-wide configuration for every other transaction.

rollbackFor / noRollbackFor: the checked-exception surprise

@Transactional // by DEFAULT, rolls back on RuntimeException and Error — NOT on checked exceptions
void transferFunds() throws InsufficientFundsException { // a checked exception
    debit();
    credit(); // if this throws InsufficientFundsException, the transaction COMMITS anyway by default!
}

@Transactional(rollbackFor = InsufficientFundsException.class) // explicitly widen the rollback rule
void transferFundsFixed() throws InsufficientFundsException { ... }

This default is a genuinely common source of production bugs: Spring's default rollback rule only covers unchecked exceptions (RuntimeException and its subclasses, plus Error) — a checked exception thrown from a @Transactional method does not trigger a rollback unless explicitly configured via rollbackFor. noRollbackFor does the reverse — exclude a specific exception type from triggering rollback even though it would otherwise (e.g. a RuntimeException subtype that represents an expected, non-fatal condition).

readOnly=true: an optimization hint, not an enforcement

@Transactional(readOnly = true)
List<Order> findAllOrders() { ... }

This tells the underlying persistence provider "no writes are expected in this transaction," which can enable real optimizations (skipping dirty-checking overhead in an ORM, routing to a read replica in some database setups) — but it is a hint, not a hard constraint. Depending on the driver/provider, an actual write inside a readOnly = true transaction might succeed anyway, fail, or behave inconsistently — it's not a reliable enforcement mechanism for "this method must not write," only a performance optimization signal.

Transaction timeout

@Transactional(timeout = 10) // seconds

If the transaction hasn't committed within the specified duration, it's forcibly rolled back — a safety net against a transaction left open indefinitely (e.g. due to a slow downstream call or a bug), which would otherwise hold database locks and resources far longer than intended.

Follow-up questions this topic invites — and their answers

Q: Why does REQUIRES_NEW suspend the outer transaction instead of just running alongside it? A: A database connection typically can't participate in two transactions simultaneously — suspending the outer transaction (releasing its connection/resources temporarily) is what makes it possible to open a genuinely separate transaction on its own connection, then resume the outer transaction once the inner one completes.

Q: If the default rollback behavior only covers unchecked exceptions, why doesn't Spring just default rollbackFor to Exception.class (covering everything)? A: This mirrors Java's own checked-vs-unchecked exception philosophy: checked exceptions are meant to represent expected, recoverable conditions the caller is expected to handle explicitly, while unchecked exceptions represent unexpected failures — Spring's default treats a checked exception as "the caller is handling this, don't assume it's transaction-fatal," which is a deliberate design choice aligned with that philosophy, even though it surprises developers coming from a mental model where any exception should obviously roll back.

Q: How does @Transactional's self-invocation bug typically get caught in practice, given it fails silently? A: Usually only via a specific, reproducible bug report ("why did this data get written even though an error occurred") or a deliberate code review checklist item — it's exactly the kind of bug that passes all normal testing if tests don't specifically assert on transactional rollback behavior, which is why some teams add an explicit lint/architecture rule flagging @Transactional methods called via this. within the same class.

Q: Does readOnly=true prevent a transaction from acquiring write locks? A: Not inherently by the JPA/Spring specification — actual behavior (whether it optimizes lock acquisition, routes to a replica, or has no effect at all) depends entirely on the specific database driver and persistence provider's interpretation of the hint, which is exactly why it should be treated as a possible optimization, not a correctness guarantee.

Previous

Spring AOP

Next

Monolith to Microservices Decomposition

AI Tutor

Lesson: @Transactional Deep Dive

Quick actions

AI responses can be inaccurate. Verify critical information.