Mastering Two-Phase Commit: Theory, XA & JTA in Java

This article explains why local transactions fail in micro‑service architectures, introduces the Two‑Phase Commit (2PC) protocol with its coordinator and participant roles, details each phase with examples, shows how XA and JTA implement 2PC in Java, and discusses its drawbacks and interview‑ready alternatives.

Xuanwu Backend Tech Stack
Xuanwu Backend Tech Stack
Xuanwu Backend Tech Stack
Mastering Two-Phase Commit: Theory, XA & JTA in Java

Why Distributed Transactions Are Needed

In monolithic applications, developers rely on the ACID guarantees of databases such as MySQL (e.g., @Transactional) to handle transactions. In a micro‑service architecture, a single business operation—like placing an order—may involve multiple services (order service, inventory service), causing local transactions to become ineffective.

Two‑Phase Commit (2PC) Overview

2PC is the classic protocol that provides atomic commit across multiple participants. It introduces two core roles:

Coordinator : the transaction initiator and overall controller, analogous to a wedding MC.

Participant : each resource manager (e.g., a database node), analogous to the bride and groom.

The fundamental principle is that all participants must answer "I agree" for the transaction to succeed; a single "I disagree" forces a rollback.

Phase 1 – Prepare (Voting)

Coordinator sends a Prepare request to all participants.

Each participant executes its local transaction, writes an Undo Log (for rollback) and a Redo Log (for recovery) to disk, and holds database locks without committing.

Participants reply with Yes (ready) or No (cannot commit).

Phase 2 – Commit (Execution)

Based on the votes, the coordinator either:

All Yes : sends Commit to participants, which then finalize the transaction and release locks, followed by an Ack response.

Any No or timeout : sends Rollback, participants use the previously stored Undo Log to revert changes and release locks, then acknowledge.

Visualizing the 2PC Process

2PC flow diagram
2PC flow diagram

XA Specification and JTA in Java

In the Java ecosystem, 2PC is not implemented manually via sockets; instead, it follows the XA specification, which defines a standard interface between resource managers (e.g., MySQL, Oracle) and transaction managers.

XA Interface : standard communication protocol for databases to participate in distributed transactions.

JTA (Java Transaction API) : Java EE (now Jakarta EE) API that abstracts XA for application developers.

XA Core Components

AP (Application Program) – the Java business code.

RM (Resource Manager) – the database that manages data.

TM (Transaction Manager) – the coordinator that controls the global transaction lifecycle.

Key XA Methods

xa_start / xa_end

: start and end a transaction branch. xa_prepare: first‑phase vote. xa_commit: second‑phase commit. xa_rollback: second‑phase rollback. xa_recover: recover pending transactions after a crash.

JTA Interfaces

javax.transaction.UserTransaction

: used by application code (e.g., Spring’s @Transactional). javax.transaction.TransactionManager: implemented by middleware vendors to manage transaction contexts.

Sample JTA‑Style Code (Using Atomikos)

import javax.transaction.UserTransaction;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.Statement;
import javax.transaction.xa.XAResource;

public class TwoPhaseCommitDemo {
    private UserTransaction userTransaction; // Coordinator
    private DataSource dataSourceA; // Participant 1
    private DataSource dataSourceB; // Participant 2

    public void transferMoney() {
        Connection connA = null;
        Connection connB = null;
        try {
            // 1. Begin global transaction (creates XID)
            userTransaction.begin();
            // 2. Participant A – debit
            connA = dataSourceA.getConnection();
            Statement stmtA = connA.createStatement();
            stmtA.executeUpdate("UPDATE account SET balance = balance - 100 WHERE id = 1");
            // 3. Participant B – credit
            connB = dataSourceB.getConnection();
            Statement stmtB = connB.createStatement();
            stmtB.executeUpdate("UPDATE account SET balance = balance + 100 WHERE id = 2");
            // 4. Phase‑2 commit
            userTransaction.commit();
            System.out.println("Distributed transaction committed!");
        } catch (Exception e) {
            try { userTransaction.rollback(); System.out.println("Distributed transaction rolled back!"); }
            catch (Exception ex) { ex.printStackTrace(); }
        } finally {
            // close connections
        }
    }
}

The userTransaction.commit() call implicitly triggers the prepare phase on all participants and then issues the commit command if every participant voted "Yes".

Why Large Companies Often Avoid 2PC/XA

Although 2PC guarantees strong consistency, it suffers from critical drawbacks in high‑concurrency internet scenarios:

Synchronous blocking : All participants hold locks throughout both phases; a slow or unresponsive participant stalls the entire transaction, degrading throughput and exhausting connection pools.

Single‑point failure : If the coordinator crashes during the commit phase, participants remain locked, leading to deadlocks until recovery.

Data inconsistency (split‑brain) : Network partitions can cause some participants to receive the commit command while others do not, resulting in divergent data states.

Interview‑Ready Follow‑up Questions

Q1: What does 3PC solve that 2PC doesn’t, and why is it still rarely used?

3PC adds a pre‑commit "CanCommit" phase and timeout mechanisms to reduce blocking, but it introduces an extra round‑trip, higher latency, and still cannot fully prevent inconsistency under network partitions. Moreover, mature middleware support is lacking.

Q2: In modern micro‑service systems, how do you ensure transactional integrity without XA/2PC?

Practices include BASE theory with eventual consistency, TCC (Try‑Confirm‑Cancel) at the application layer, reliable message queues (e.g., RocketMQ transactional messages), and open‑source solutions like Seata (AT mode) that combine global locks and undo logs to avoid long‑lasting database locks.

Javamicroservices2PCdistributed transactionsXAJTA
Xuanwu Backend Tech Stack
Written by

Xuanwu Backend Tech Stack

Primarily covers fundamental Java concepts, mainstream frameworks, deep dives into underlying principles, and JVM internals.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.