A robust, transactional backend service built using Spring Boot 3.x and Java 21 that supports wallet-to-wallet transfers.
This implementation prioritizes exactly-once API semantics (idempotency), database consistency (double-entry ledger), and safe concurrent execution (deadlock prevention & locking).
- Language: Java 21
- Framework: Spring Boot 3.x, Spring Data JPA
- Database: H2 Database (configured in PostgreSQL compatibility mode for zero-setup execution)
- Build System: Maven (wrapper included)
The database uses H2 (in-memory) with schemas created automatically on startup. The schema includes four main tables:
wallets: Contains the account balance with a database constraint (balance >= 0) ensuring no wallet can ever drop below zero.transfers: Records each transfer request, its state (PENDING,PROCESSED,FAILED), and any error messages.ledger_entries: A double-entry ledger recording all debits and credits. Every transfer generates exactly two entries (one debit and one credit) to ensure the system balances.idempotency_records: Stores API response bodies and HTTP status codes mapped to client-provided idempotency keys.
To handle concurrent requests safely without race conditions or double spending:
- Pessimistic Write Locking: We acquire a database-level lock (
SELECT ... FOR UPDATEvia JPA@Lock(LockModeType.PESSIMISTIC_WRITE)) on the source and destination wallets before validating balances and executing the transfer. - Deadlock Avoidance: When locking two wallets, we sort their IDs lexicographically. We always lock the wallet with the smaller ID first, then the larger ID. This prevents circular dependencies (deadlocks) when concurrent transactions attempt transfers between the same accounts in opposite directions.
- When a request is received, the service checks
idempotency_recordsfor the key. - If it exists, the cached response is instantly returned.
- If it doesn't exist, the transaction is executed. The database enforces a
UNIQUEconstraint on the idempotency key. Any concurrent duplicate request trying to execute at the same microsecond will fail the DB insert, throwing a unique constraint violation which we catch and resolve by returning the correct processed result.
- Java 21 installed on your system.
- Navigate to the project root directory.
- Build and run the server:
.\mvnw.cmd spring-boot:run - Once started, the server listens on port
8080.
We have built a thorough test suite covering edge cases, state transitions, validation, and multi-threaded concurrency.
To execute the test suite:
.\mvnw.cmd clean testWalletApplicationTests: Verifies application context loading.TransferConcurrencyTest: Spawns multiple concurrent threads attempting to transfer money from the same source wallet simultaneously to verify thread-safety and prevent double spending.TransferControllerTest: Asserts the REST endpoints, validation errors, non-existent wallet handling, and idempotency replay behavior.
-
Endpoint:
POST /transfers -
Headers:
Content-Type: application/json -
Request Body:
{ "idempotencyKey": "unique-uuid-key", "fromWalletId": "wallet_1", "toWalletId": "wallet_2", "amount": 150.00 } -
Successful Response (201 Created):
{ "transferId": "48b625ca-d84d-4523-a55e-990c0ef48e24", "fromWalletId": "wallet_1", "toWalletId": "wallet_2", "amount": 150.00, "status": "PROCESSED", "errorMessage": null } -
Insufficient Funds Response (422 Unprocessable Entity):
{ "transferId": "9127814b-22fb-4e1b-b27a-85d8d1e21b24", "fromWalletId": "wallet_1", "toWalletId": "wallet_2", "amount": 5000.00, "status": "FAILED", "errorMessage": "insufficient funds" }
While the application is running, you can inspect the database directly:
- Console URL: http://localhost:8080/h2-console
- JDBC URL:
jdbc:h2:mem:walletdb - Username:
sa - Password: (leave blank)
As requested by the assignment guidelines:
- Tool Used: Antigravity (Advanced Agentic Coding assistant developed by Google DeepMind).
- Usage Strategy: Antigravity was used as a senior developer pair-programmer. It assisted in analyzing the initial workspace, creating the architecture and design plan, verifying compile success and Maven wrapper commands, and structuring the end-to-end execution guide.
- Session Transcript: The full interaction history and prompts are maintained locally in the system generated logs, and a walkthrough log is provided in the repository under walkthrough.md.