LESSON 20 · BEGINNER4 min read

Guided Project 1: Thread-Safe Bank Account Transaction Ledger

Construct a thread-safe financial ledger. Model immutable transaction records, defend account balance invariants, and execute concurrent deposits and transfers.

OpenJDK 21+ (LTS)Free to readModule 5 of 15
YOUR NEXT SMALL WINS

By the end, you’ll be able to…

  • Architect a thread-safe multi-account banking ledger using Java 21 Records and ReentrantLocks.
  • Implement deadlock-free atomic account transfers using lock ordering.
  • Generate immutable audit trails and compute financial metrics using the Stream API.

01Project Architecture & Domain Model

In this first guided project, we engineer a robust, thread-safe financial transaction ledger. Financial systems have zero tolerance for race conditions, negative balances, or dropped audit logs.

We model our domain using Java 21 Records for immutable transactions and sealed classes for operation results. Every financial mutation is recorded as an immutable Transaction record, ensuring an append-only audit trail.

example.js☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

02Thread-Safe Balance and Lock Ordering

When performing an atomic transfer between Account A and Account B in a multi-threaded environment, acquiring locks in arbitrary order leads directly to Deadlock! If Thread 1 transfers A -> B (locking A, waiting for B) while Thread 2 transfers B -> A (locking B, waiting for A), both threads freeze forever.

To eliminate deadlocks, we implement Lock Ordering: we sort the account IDs lexicographically and always acquire locks in strict canonical order, regardless of which account is the source or destination.

example.js☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

03Deadlock-Free Transfer Implementation

Our transfer method determines which account has the lower ID and locks that account first. It then acquires the lock on the second account. Once both locks are securely held, it validates that the source account has sufficient funds, updates balances atomically, and generates the immutable transaction log.

Finally, both locks are cleanly released inside a nested finally block, guaranteeing that no lock is orphaned even if an unchecked exception occurs.

example.js☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

04Why This Matters in Production

Lock ordering and immutable record journals are the foundational principles powering modern distributed core banking platforms (such as Stripe's ledger and high-frequency exchange matching engines).

By implementing strict lock ordering, zero deadlocks can ever occur. And by modeling transactions with Java 21 Records, your ledger state is easily serialized to PostgreSQL, audited with Stream pipelines, and verified by external financial regulators.

05Production Architecture & Engineering Trade-Offs

Building an industrial-grade production project requires rigorous engineering beyond passing unit test suites. Real-world systems must handle partial network failures, malformed input streams, concurrency race conditions, and graceful shutdown sequences without data corruption or memory leaks.

When developing this project, ensure that domain boundaries are clearly defined, errors are modeled with typed exceptions or sealed result types rather than generic runtime errors, and performance critical paths are profiled to verify zero unnecessary heap allocations and predictable latency profiles.

Furthermore, real-world deployments mandate structured observability: instrument latency percentiles (p50, p95, p99) using Micrometer metrics, log contextual correlation identifiers across asynchronous thread boundaries, and implement health-check readiness probes for Kubernetes container orchestration.

Finally, validate defensive resource cleanup: all file descriptors, network sockets, and database connections must be managed via try-with-resources blocks to guarantee deterministic reclamation even when unhandled exceptions occur during critical processing loops.

LET IT CLICK

The ideas worth keeping.

  • Store monetary amounts as integer cents (long) to avoid floating-point rounding errors.
  • Enforce strict lock ordering to eliminate deadlocks during concurrent multi-resource transfers.
  • Model immutable financial event logs using Java 21 Records and sealed results.
TURN UNDERSTANDING INTO SOMETHING YOU CAN DO

Make it yours.

Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.

01

Exercise 1: Audit Stream Summary

Write a method that processes a List<Transaction> using the Stream API to return the total transaction volume transferred in cents.

your_turn_1.java☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.javaJava
public static long calculateTotalVolume(List<Transaction> transactions) {    return transactions.stream()        .mapToLong(Transaction::amountInCents)        .sum();}

mapToLong avoids boxing overhead and efficiently sums financial amounts.

02

Exercise 2: Concurrent Multi-Transfer Test

Write a unit test that simulates 1,000 concurrent cross-transfers between two accounts using Virtual Threads, asserting that the combined total balance remains perfectly conserved.

your_turn_2.java☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.javaJava
try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {    long initialCombined = accA.balance() + accB.balance();    for (int i = 0; i < 1000; i++) {        exec.submit(() -> ledger.transfer(accA, accB, 10));        exec.submit(() -> ledger.transfer(accB, accA, 10));    }    // Combined total balance must strictly equal initialCombined}

Total funds across accounts remain constant, proving conservation of money and deadlock-free thread safety.

A SMALL QUESTION. A USEFUL NEXT STEP.

What stayed with you?

Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.

A LITTLE ACTIVE RECALL0 / 3 answered
01How does canonical Lock Ordering prevent deadlocks during two-account transfers?
02Why are financial transaction records represented as Java 21 Records?
03Why is money represented in cents (long) rather than dollars (double) in banking ledgers?
⚡CODING CHALLENGE
🔒Required to Mark Lesson Complete

Exercise 1: Audit Stream Summary

Write a method that processes a List<Transaction> using the Stream API to return the total transaction volume transferred in cents.

EXPECTED OUTPUT
Build successful. (Java 15)
[Process exited normally]
Main.javaJava
Ctrl + ↵
Execution Console
Click "Run & Verify" (or press Ctrl + Enter) to test your code against the challenge.

Keep exploring.

The official references behind this lesson, when you want a second angle or the full details.

MODULE 05 · PRACTICAL SELF-CHECK

Pause. Put the pieces together.

You’ve reached the end of The Collections Framework, Stream Pipelines & Financial Ledger Project. Before moving on, use this task to check what you can do independently.

Deliver Project 1: High-Throughput Bank Ledger Transaction System.

Try it in your own environment and write down the result. This is a self-check; reading completion does not evaluate your code.

Review this module

One lesson closer.

Reading is a start. Trying it yourself makes the connection.

Solve the hands-on coding challenge in the knowledge section to verify your code and unlock lesson completion.