Passwd: Kumar@123Double Ledger Banking System
Transaction-Safe Banking Backend
A banking backend designed around double-entry accounting, transactional correctness, concurrency control, idempotency and secure API design.
Credentials for authorized evaluation.
These are non-production test accounts. Customer registration and account opening still follow the bank onboarding workflow.
Passwd: Gaurav@123Double Ledger models money movement as a financial operation rather than a simple balance update. A transfer records a debit, a credit and the transaction that binds both sides together.
A CRUD banking application can appear correct in a happy-path demo while remaining unsafe under retries, concurrent requests or partial failure. This project focuses on correctness, consistency, concurrency, security, idempotency and auditability.
How the system holds together.
Engineering Challenges
Transfers must preserve the relationship between sender and receiver inside one database transaction. Concurrent requests require protected account updates, while deterministic ordering reduces deadlock risk when more than one account is locked.
- Transactional correctness keeps debit and credit together.
- Pessimistic locking protects concurrent account updates.
- Idempotency makes retried transfer requests safe.
- JWT authentication, refresh-token rotation and RBAC protect the API.
- Audit logs make important operations traceable.
Double-Entry Ledger
Every financial transaction produces a DEBIT for the sender and a CREDIT for the receiver. Both entries belong to the same transaction, so the movement can be reconciled and traced instead of relying on an unexplained balance mutation.
- Account A: -₹500
- Account B: +₹500
Concurrency Control
Multiple requests can arrive at the same time. Account balances cannot be safely updated without concurrency control. Row-level locks protect shared account state inside a database transaction; deterministic ordering reduces deadlock risk, but does not itself solve race conditions.
Fund Transfer Flow
The transfer path validates the request, authenticates the caller, checks idempotency, loads accounts, acquires locks, validates balance, ownership and status, creates the transaction and ledger entries, updates account state, and commits. Validation, lock or persistence failures can roll the transaction back.
Idempotency
Payment-like requests may be retried when a client times out. A clientRequestId lets the API distinguish an existing operation from a new one: an existing result is returned, while a new request is processed and its result saved before responding.
Security
The documented security design uses Spring Security, JWT access tokens, refresh-token rotation, BCrypt password hashing and role-based access control. Authorization belongs at the protected API boundary as well as in the application flow.
Customer Onboarding & Test Access
The banking system does not provide open public customer signup. An authorized Admin, Manager or Staff user must register the customer with the bank first; an account can only be opened after that customer registration exists. The credentials below are non-production test accounts for authorized evaluation.
Failure Scenarios
Insufficient balance, invalid accounts, unauthorized access, duplicate requests, validation failures, concurrent updates and transaction failures are treated as explicit failure paths. Database rollback prevents a partial transfer from becoming visible.
REST API Design
The API is organized around authentication, accounts, transfers, transactions, users, administration and audit concerns. Exact endpoint names are intentionally omitted here because this repository contains the portfolio, not the backend source of truth.
System surfaces.








Why I made these decisions.
Why PostgreSQL?
Relational transactions and constraints fit financial state that must remain consistent.
Why Spring Boot?
It provides a focused foundation for REST APIs, dependency injection, security and data access.
Why pessimistic locking?
Account updates need protected shared state when concurrent transfers target the same rows.
Why deterministic ordering?
Acquiring multiple account locks in a consistent order reduces deadlock risk.
Why double-entry accounting?
A debit and credit provide a traceable, balanced representation of money movement.
Why idempotency?
Retries should return the existing result instead of creating a second operation.
Why JWT and refresh rotation?
Short-lived API access and replaceable refresh credentials support protected sessions.
Why RBAC?
Authorization rules can express which authenticated users may perform protected operations.
JUnit, Mockito, Postman and Swagger / OpenAPI are the documented testing and API tools. The testing focus is business logic, validation, authentication and authorization, transfer behavior, failure scenarios, idempotency and concurrency-sensitive logic where applicable. Because public signup is disabled, live testing requires authorized Admin, Manager or Staff credentials to register a customer before opening an account.
The application is deployed with Vercel for the frontend, Render for the Spring Boot backend, and Neon PostgreSQL for the database. GitHub Actions provides the CI/CD pipeline, with Docker included in the delivery workflow.
Lessons that travel.
- Database transactions are part of business correctness.
- Concurrency problems cannot be solved only at the API layer.
- Idempotency matters for retryable operations.
- Security requires both authentication and authorization.
- Financial operations require stronger consistency guarantees than ordinary CRUD operations.