Introduction
In a modern application that interacts with different database and app services, handling the distributed transactions are very efficient and crucial. To manage the data consistency across the multiple resources XA transaction(eXtended Architecture) is a standard way.
In this article, we will explore how to use the XA Transactions in Java using Hazelcast data grid.

What are XA Transactions?
XA Transactions are defined by the X/Open Group, it is a protocol that enables features to manage the transactions across multiple resource managers (like databases, message queues, etc.). Transactions are managed and co-ordinates by the transaction manager(TM). XA Transactions are integrated with multiple platforms and frameworks.
Why Use XA Transactions?
- If you need to achieve consistency across the multiple databases, XA Transaction ensures to manage the transaction.
- XA Transactions support distributed transaction management, for example – Hazelcast and database storage transaction management.
- It also prevents partial commits in case of failures.
Understanding Two-Phase Commit (2PC)
XA Transactions in Java using Hazelcast in-memory data grid, rely on two-phase commits protocol:
- Phase1 – Prepare: The Transaction Manager manages all resources and it asks the resources to prepare.
- Phase2 – Commit/Rollback: If the execution process succeeds in all resources, then the transaction gets committed, otherwise it gets rolled back.
How Hazelcast Supports XA Transactions
XA Transactions in Java using Hazelcast has in-built HazelcastInstance and XA resources, that support XA Transaction feature, using using any third party library like <groupId>com.atomikos</groupId> transaction management. This acts as a cache data store and it also participates in XA transactions with RDBMS.
Setting Up Hazelcast for XA Transactions
To understand the implementation of XA Transactions in Java using Hazelcast, here is the code example we are demonstrating user, which are persisted into the Hazelcast Map and Persistence database.
To setup the Hazelcast environment, you can refer the following articles.
- How to Integrate Hazelcast with Spring Boot?
- How to Connect a Database with Hazelcast Using Hikari Connection Pooling?
- Understanding of Hazelcast QueueStore
- Understanding Hazelcast MapLoader
- Understanding of Hazelcast MapStore
- How to Start Hazelcast Members and Client?
Add the Required Dependencies
Add the maven dependencies for Hazelcast and Transaction Management
<dependencies> <dependency> <groupId>com.hazelcast</groupId> <artifactId>hazelcast</artifactId> <version>5.3.0</version> </dependency> <dependency> <groupId>com.hazelcast</groupId> <artifactId>hazelcast-xa</artifactId> <version>5.3.0</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.0.1</version> </dependency> <dependency> <groupId>org.h2database</groupId> <artifactId>h2</artifactId> <version>2.1.214</version> </dependency> </dependencies>
XA Transaction with Database
The following are a code snippet where we are demonstrating the XATransaction, where data is written into Hazelcast in the Database.
package com.javatecharc.demo.hazelcast.transaction; import com.hazelcast.config.Config; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.transaction.TransactionContext; import com.hazelcast.transaction.TransactionalMap; import com.hazelcast.xa.HazelcastXAResource; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import java.sql.Connection; import java.sql.PreparedStatement; public class HazelcastXADemoExample { public static void main(String[] args) throws Exception { // Initialize Hazelcast instance Config config = new Config(); HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config); // Obtain XA Resource from Hazelcast HazelcastXAResource xaResource = hazelcastInstance.getXAResource(); // Configure Database (H2 + HikariCP) DataSource dataSource = getDataSource(); Connection connection = dataSource.getConnection(); connection.setAutoCommit(false); // Create a unique transaction ID (Xid) Xid xid = new SampleXid(123, new byte[]{0x01}, new byte[]{0x02}); // Start XA Transaction xaResource.start(xid, XAResource.TMNOFLAGS); // Begin Hazelcast Transaction TransactionContext context = hazelcastInstance.newTransactionContext(); context.beginTransaction(); // Hazelcast Operation TransactionalMap<Integer, String> txMap = context.getMap("JavaTechARCXAMap"); txMap.put(1, "Hazelcast XA Transaction"); // Database Operation PreparedStatement stmt = connection.prepareStatement("INSERT INTO users (id, name) VALUES (?, ?)"); stmt.setInt(1, 1); stmt.setString(2, "XA User"); stmt.executeUpdate(); // Prepare phase for two-phase commit xaResource.end(xid, XAResource.TMSUCCESS); int prepare = xaResource.prepare(xid); if (prepare == XAResource.XA_OK) { // Commit Hazelcast Transaction xaResource.commit(xid, false); context.commitTransaction(); // Commit Database Transaction connection.commit(); System.out.println("XA Transaction committed successfully!"); } else { // Rollback if any issue xaResource.rollback(xid); context.rollbackTransaction(); connection.rollback(); System.out.println("XA Transaction rolled back!"); } // Cleanup connection.close(); hazelcastInstance.shutdown(); } // Database Configuration using HikariCP private static DataSource getDataSource() { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"); config.setUsername("sa"); config.setPassword(""); return new HikariDataSource(config); } }
Sample XId class implementation
An implementation of XId class where XA Transaction require identification.
package com.javatecharc.demo.hazelcast.transaction; import javax.transaction.xa.Xid; import java.util.Arrays; public class SampleXid implements Xid { private final int formatId; private final byte[] globalTransactionId; private final byte[] branchQualifier; public SampleXid(int formatId, byte[] globalTransactionId, byte[] branchQualifier) { this.formatId = formatId; this.globalTransactionId = globalTransactionId; this.branchQualifier = branchQualifier; } @Override public int getFormatId() { return formatId; } @Override public byte[] getGlobalTransactionId() { return globalTransactionId; } @Override public byte[] getBranchQualifier() { return branchQualifier; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; SampleXid that = (SampleXid) obj; return formatId == that.formatId && Arrays.equals(globalTransactionId, that.globalTransactionId) && Arrays.equals(branchQualifier, that.branchQualifier); } @Override public int hashCode() { return Arrays.hashCode(globalTransactionId) + Arrays.hashCode(branchQualifier); } }
The Code Explanation
- Hazelcast Instance: Create Hazelcast instance from distributed clusters.
- XA Resource: Create XAResource for the database connection and data storage
- XA Transaction (Two-Phase Commit)
- Start the XA transaction.
- Perform a transactional write operation on Hazelcast.
- Insert a record in the H2 database.
- Call prepare() to check if all operations can commit.
- If successful, commit both Hazelcast and database operations.
- If any error occurs, rollback everything.
- Xid Implementation: Create unique identified for transaction
Output (Successful Transaction)
On successful transaction print the message:
XA Transaction committed successfully!
Output (Rollback Scenario)
If any error then rollback the transaction
XA Transaction rolled back!
Performance Considerations
- XA Transactions introduce may use more memory due to 2PC.
- Avoid XA with single resources
- Use HikariCP or similar connection pooling to improve efficiency.
Best Practices for XA Transactions
- Prefer local transactions where it is possible.
- Use JTA-compliant transaction managers, e.g. atomikos.
- Try to avoid lock the resources
- Implement and monitor the logs
Conclusion
XA Transactions in Java using Hazelcast provide a way to manage the distributed transactions across multiple resources. Java developer can build a reliable and consistent application by using the these capabilities
XA Transactions allow atomic operations across the resources, like Hazelcast and multiple database
HazelcastXAResource ensures Hazelcast should be part of the transaction and resource management.
HikariCP with H2 manages database transactions and persistence efficiently.
Two-Phase Commit (2PC) manages the transactions.
More detailed implementation of the Java code example is available on GitHub.