Banking operations. Balanced entries. Explicit rules.
A Java library for banking operations and double-entry accounting.
Java 25 · PostgreSQL · Redis · 0.1.0-SNAPSHOT
Get started · Explore the example · Read the architecture
Represent deposits, transfers, withdrawals, and fees as balanced journal entries. N2Banking Core brings together monetary values, account operations, and PostgreSQL persistence behind a Java facade that your backend initializes and calls.
Every journal entry must balance. Account balances are derived from persisted postings. Currency is carried with the amount, and addition and subtraction reject currency mismatches.
This repository is an embeddable library. Your backend owns HTTP endpoints, authentication, authorization, startup, and shutdown. The current snapshot is experimental and is not intended to manage real funds.
| Explicit money | Balanced entries | Recorded history |
|---|---|---|
BigDecimal amounts paired with a currency. |
At least two positive postings, one currency, equal debit and credit totals. | PostgreSQL triggers reject journal updates and deletes; reversals append new entries. |
| Explore monetary values | Inspect the invariants | Read the architecture |
flowchart LR
backend["Your backend<br/>Endpoints · Authorization<br/>Startup · Shutdown"]
core["N2Banking Core<br/>Accounts · Operations · Fees<br/>Money · Journal entries"]
postgres[("PostgreSQL<br/>Journal and accounts")]
redis[("Redis<br/>Balance cache")]
backend -->|Java API| core
core -->|Persistence| postgres
core -.->|Best-effort caching| redis
One process-wide facade connects the operation services to their persistence and cache adapters. PostgreSQL stores the journal; Redis accelerates balance reads with a best-effort cache. See the component boundaries.
With the library initialized, accounts funded, and system accounts registered:
// Create once per logical operation; retain the key and inputs for a retry.
var command = new TransferCommand(
new IdempotencyKey("payment-4821"),
aliceAccountId, bobAccountId,
new Money("125.00", "EUR"), "payment-4821");
OperationResult result = bank.transfer(command);
JournalEntry entry = result.journalEntry();
// A retry with the same key and inputs returns the original entry;
// result.replayed() identifies the replay.With zero fees, the entry records:
| Account | Debit | Credit |
|---|---|---|
| Alice's customer liability | EUR 125.00 | — |
| Bob's customer liability | — | EUR 125.00 |
| Total | EUR 125.00 | EUR 125.00 |
A debit reduces Alice's liability balance; a credit increases Bob's. With a transfer fee, the sender is still debited EUR 125.00 and the recipient receives that amount minus the fee.
See the complete integration example for imports, initialization, account creation, funding, and shutdown. Retry behavior differs between command replay and the deprecated entry-based overloads.
| Capability | Current behavior |
|---|---|
| Monetary values | BigDecimal amounts with an explicit currency; addition and subtraction reject currency mismatches |
| Journal entries | At least two positive postings, a single currency, equal debit and credit totals |
| Banking operations | Deposits, transfers, withdrawals, explicit fee charges, and reversal entries |
| Persistence | PostgreSQL customer, account, journal, balance, and statement adapters |
| Reads | Account balances, as-of balances excluding future-dated postings, complete entries in date-bounded statements, and totals by account type and currency |
| Retry handling | Deposit, transfer, withdrawal, fee and reversal commands atomically claim a key and replay the committed result; legacy methods retain entry-based retry behavior. See operation idempotency. |
| Cache | Redis balance reads with a default 30-second TTL and invalidation after journal commits |
The invariants guide links these rules to implementation and distinguishes existing tests from coverage gaps. There is one logical ledger per database, with no tenant or ledger identifier.
Install JDK 25 or newer and Maven, then run from the repository root:
mvn -DskipTests installThis builds the JAR and installs com.n2bank:n2bank-core:0.1.0-SNAPSHOT into your local Maven repository. The getting-started guide covers local services, schema setup, and running the example.
Before running mvn test: integration tests start their own PostgreSQL/Redis via Testcontainers (Docker required) in disposable schemas, leaving your databases untouched. Set N2BANK_TEST_URL/N2BANK_TEST_USER/N2BANK_TEST_PASSWORD (and N2BANK_TEST_REDIS_URI for the facade workload) to reuse an external database instead — see Testing.
| Guide | Use it to |
|---|---|
| Getting started | Build locally, configure services, and execute the library example |
| Backend integration | Manage lifecycle, account setup, operations, fees, retries, and errors |
| Operations and idempotency | Claim keys, replay results, fingerprints, and upgrade the schema |
| Accounting model | Understand money, postings, balance signs, and reversals |
| Architecture | Navigate the source and understand component responsibilities |
| Invariants | Inspect enforcement boundaries and supporting evidence |
| Testing | Run tests safely and understand what they cover |
| Diagrams | Visual index of all Mermaid charts across the guides |
There is no bundled backend server, payment-rail integration, recurring fee scheduler, or automatic migration runner. Fresh databases apply database/schema.sql; existing databases apply the manual scripts in database/migrations/ — see schema installation.
MIT — see LICENSE.
