skip to content

JtaTransactionManager & XA

JtaTransactionManager spans several XA resources with two-phase commit, at the cost of latency, blocking and operational pain. Interviewers usually raise it so you can argue for a Saga or an outbox instead.

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

questions

5

Explain the two-phase commit (2PC) protocol that JtaTransactionManager uses across XA resources.

level: middleimportance: must knowfreq 55%

answer

  1. prepare = vote, commit = decision
  2. vote YES then in-doubt, locks held
  3. coordinator tx-log = point of no return
  4. 1PC / read-only / presumed-abort optimizations
  5. coordinator crash after prepare = blocking + XA recovery

basics

~20 s

2PC has two rounds driven by a coordinator. Phase 1 (prepare): every resource is asked to prepare and votes commit or abort, durably persisting so it can honor the vote. Phase 2 (commit/abort): if all voted commit, the coordinator tells all to commit; otherwise all roll back.

solid answer

~50 s

Two-phase commit is how a JTA coordinator (Atomikos/Narayana) makes multiple XA resources commit atomically. In **phase one (prepare)** the coordinator calls `prepare` on each enlisted resource; each does its work durably and votes YES (ready) or NO (must abort). Once a resource votes YES it must be able to commit even after a crash. In **phase two** the coordinator writes its decision to a transaction log, then, if all voted YES, calls `commit` on every resource; if any voted NO or timed out, it calls `rollback` on all. The transaction log lets a crashed coordinator recover in-doubt transactions on restart. The costs: extra round-trips (latency), and between prepare and commit each resource holds locks and is 'in-doubt' — if the coordinator dies then, resources block until recovery resolves them. That blocking window is 2PC's core weakness.

code

java · 19 lines
java
// You never call prepare/commit yourself — the JTA coordinator does.
// Conceptually, behind one @Transactional the coordinator runs:
//
//   // PHASE 1 - prepare (vote)
//   int v1 = dbResource.prepare(xid);   // XA_OK or vote rollback
//   int v2 = jmsResource.prepare(xid);
//
//   if (v1 == XA_OK && v2 == XA_OK) {
//       txLog.writeDecision(xid, COMMIT); // durable point of no return
//       // PHASE 2 - commit
//       dbResource.commit(xid, /*onePhase=*/false);
//       jmsResource.commit(xid, false);
//   } else {
//       dbResource.rollback(xid);
//       jmsResource.rollback(xid);
//   }
//
// XAResource.prepare / commit / rollback / recover are the SPI
// that XADataSource and XAConnectionFactory implement.

go deeper

for a junior

Just the two-round idea: prepare/vote then commit/abort, all-or-nothing.

for a middle

Explains the tx log, in-doubt window, and why locks are held — core expected depth.

for a senior

Adds optimizations (1PC, read-only, presumed abort) and XA recovery mechanics.

for a principal

Frames 2PC as a blocking CP protocol whose in-doubt/latency cost motivates Saga/outbox architectures and deployment constraints (durable tx log).

## Why two phases To commit N resources atomically you can't just commit them one by one — the second could fail after the first already committed. 2PC solves this by splitting commit into a **vote** then a **decision**, so no resource permanently commits until *all* have promised they can. ## The actors - **Coordinator / Transaction Manager**: Atomikos, Narayana, or the app-server TM. Spring's `JtaTransactionManager` delegates to it. - **Participants / resource managers**: each XA resource (XADataSource connection, XA JMS session), reached through the XA `Resource` interface (`XAResource.prepare/commit/rollback`). - **Transaction log (tx log / recovery log)**: durable store where the coordinator records the outcome decision. ## Phase 1 — Prepare (voting) For each enlisted resource the coordinator calls `prepare()`: - The resource does all durable work needed so it can *definitely* commit later — flushes redo, writes to disk — and holds its locks. - It replies **XA_OK (vote commit)** or throws/votes rollback. - A read-only resource may answer **XA_RDONLY** and drop out early (an optimization). After voting commit, a participant is **in-doubt**: it has given up the right to unilaterally decide and must wait for the coordinator, keeping locks held. ## Phase 2 — Commit or abort (decision) - If **all** votes are commit, the coordinator **first writes 'commit' to its tx log** (the point of no return), then calls `commit()` on each resource. - If **any** vote is abort (or a prepare times out), it calls `rollback()` on all. - Resources acknowledge; the coordinator forgets the transaction. ## Optimizations - **One-phase commit (1PC) / last-resource optimization**: if only one resource is enlisted, the coordinator skips prepare and commits directly — no 2PC cost. This is why a single-resource JTA transaction is cheap. - **Read-only optimization**: resources that only read vote XA_RDONLY and are excluded from phase two. - **Presumed abort**: if the coordinator has no log record, recovery presumes the transaction aborted, saving log writes for the common rollback case. ## Failure / recovery - If the **coordinator crashes after prepare but before/while deciding**, participants are stuck in-doubt with locks held. On restart the coordinator reads its tx log and drives the remaining commits/rollbacks — this is **XA recovery** (`XAResource.recover`). Atomikos/Narayana run a periodic recovery scan. - If a **participant crashes after voting commit**, on restart it reports its in-doubt transactions to the coordinator via `recover()` and awaits the decision. - **Heuristic outcomes**: an operator or timeout can force an in-doubt resource to commit/rollback independently, risking a **heuristic mixed/hazard** result — a real inconsistency 2PC is supposed to prevent. These surface as `XAException` heuristics and require manual resolution. ## Costs and gotchas (interview gold) - **Latency**: two network round-trips + tx-log fsync per resource. - **Blocking**: the in-doubt window holds locks; a coordinator outage can freeze rows/queues until recovery. - **Not partition-tolerant**: 2PC assumes participants come back; it is a CP, blocking protocol. - **Requires durable coordinator storage**: the tx log must survive crashes, which complicates containerized/ephemeral deployments (needs a stable volume + unique node id). - This blocking/latency is precisely why teams reach for **Sagas / outbox** instead (separate question).

  • What is an 'in-doubt' transaction and why is it dangerous?
    After a resource votes commit in phase one but before it receives the coordinator's phase-two decision, it is in-doubt: it holds locks and cannot decide alone. If the coordinator crashes in that window, the resource blocks (locks held) until XA recovery reads the coordinator's tx log and resolves it.
  • How does 2PC recover after a coordinator crash?
    The coordinator persists its commit/rollback decision to a durable transaction log before phase two. On restart it replays the log and calls XAResource.recover() on each resource to find in-doubt transactions, then drives them to the logged outcome. This is why the tx log must be on stable storage.

saying these in an interview costs you the question

  • Saying 2PC guarantees no blocking / no locks (the in-doubt window blocks and holds locks).
  • Claiming 2PC never fails or is fully partition-tolerant (a coordinator crash after prepare stalls participants).
  • Confusing prepare with the actual commit — prepare is a vote, not the commit.

context

open as a page

What is JtaTransactionManager in Spring and when do you need it instead of DataSourceTransactionManager?

level: juniorimportance: should knowfreq 45%

basics

~20 s

JtaTransactionManager is a Spring PlatformTransactionManager that delegates to a JTA provider so one @Transactional can span multiple resources (e.g. a database and a JMS broker) and commit them together. You need it only when a single transaction touches more than one resource.

open as a page

How would you configure a Spring Boot application to run one transaction spanning a database and a JMS broker with Atomikos or Narayana?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Add a JTA provider starter (Atomikos or Narayana). Configure the DataSource as an XADataSource and the JMS ConnectionFactory as XA. Spring Boot auto-creates a JtaTransactionManager and enlists both, so a plain @Transactional method commits DB and JMS together via 2PC.

open as a page

What are the concrete performance and availability costs of using XA/2PC, and how do they show up in production?

level: seniorimportance: should knowfreq 30%

basics

~20 s

2PC adds latency (extra prepare/commit round-trips plus a durable tx-log write per transaction) and holds locks during the in-doubt window between prepare and commit. If the coordinator or a resource stalls, locked rows/queues block until recovery, hurting throughput and availability.

open as a page

When would you choose a Saga (or transactional outbox) over JtaTransactionManager/XA, and what are the trade-offs?

level: principalimportance: should knowfreq 40%

basics

~20 s

Use XA/2PC when you need immediate strong atomicity across a few co-located XA resources and can accept its latency and blocking. Prefer a Saga (or outbox) when resources aren't XA-capable, services are independent/scaled out, or you need availability — trading atomicity for eventual consistency with compensating actions.

open as a page