Skip to content

Repository files navigation

LedgerGuard

CI Java 21 Kafka 4.1

LedgerGuard is a real-time financial transaction reconciliation system built with Java, Spring Boot, and Kafka Streams. It correlates independently produced payment and ledger events, identifies inconsistencies, and emits an auditable outcome stream.

The project uses synthetic data and rules. It contains no employer code, proprietary calculations, or internal system details.

What it demonstrates

  • Event-time stream processing and out-of-order event handling
  • Stateful Kafka Streams joins
  • Exactly-once processing
  • Bounded, persistent event-ID deduplication
  • Explicit exception routing
  • Idempotent Kafka production
  • Same-origin operations dashboard backed by the live event stream
  • User-defined payment and ledger events with an inspectable processing trace
  • Public-demo input validation, concurrency protection, and rate limiting
  • Metrics with Spring Boot Actuator, Micrometer, and Prometheus
  • Topology, domain, simulator, dashboard, and real-broker tests

Architecture

flowchart LR
    G["Browser reconciliation console"] -->|"POST scenario"| A["Demo API / scenario generator"]
    A -->|"payments.v1"| B["Apache Kafka"]
    A -->|"ledger-entries.v1"| B
    B --> C["Kafka Streams reconciliation engine"]
    C -->|"reconciliations.v1"| B
    C -->|"reconciliation-exceptions.v1"| B
    B --> D["In-memory demo result projection"]
    D --> E["Reconciliation REST API"]
    E --> G
    C --> F["Actuator / Prometheus metrics"]
Loading

The system is intentionally split into three focused modules:

Module Responsibility
ledgerguard-contracts Immutable payment, ledger, and reconciliation event contracts
ledgerguard-reconciliation Deduplication, event-time joins, classification, routing, and metrics
ledgerguard-demo-api Interactive transaction workbench, scenario generation, processing trace, and a queryable projection of recent outcomes

Reconciliation outcomes

Status Meaning
MATCHED Amount and currency agree
AMOUNT_MISMATCH Currencies agree but amounts differ
CURRENCY_MISMATCH Payment and ledger currencies differ
MISSING_LEDGER_ENTRY A payment has no ledger counterpart when the join window closes
MISSING_PAYMENT A ledger entry has no payment counterpart when the join window closes
DUPLICATE_PAYMENT A payment event ID was already observed
DUPLICATE_LEDGER_ENTRY A ledger event ID was already observed

Run locally

Requirements:

  • Java 21
  • Docker Desktop or another Docker-compatible runtime

Build and start Kafka:

./mvnw clean verify
docker compose up -d --wait

Start the reconciliation engine:

java -jar ledgerguard-reconciliation/target/ledgerguard-reconciliation-0.1.0-SNAPSHOT.jar

In another terminal, start the demo API:

java -jar ledgerguard-demo-api/target/ledgerguard-demo-api-0.1.0-SNAPSHOT.jar

Open http://localhost:8080 to use the interactive reconciliation console. You can enter payment and ledger values, remove either side, change event order and delay, duplicate a payment, and then inspect the real records and decisions at every processing stage. Seven repeatable scenarios are also included.

The REST API remains available for command-line use:

curl -X POST http://localhost:8080/api/scenarios/amount-mismatch
curl http://localhost:8080/api/reconciliations

Submit your own event pair:

curl -X POST http://localhost:8080/api/transactions \
  -H 'Content-Type: application/json' \
  -d '{
    "transactionId": "RECRUITER-DEMO-01",
    "paymentAmount": 275.00,
    "paymentCurrency": "CAD",
    "ledgerAmount": 271.50,
    "ledgerCurrency": "CAD",
    "eventOrder": "LEDGER_FIRST",
    "eventDelayMs": 500,
    "duplicatePayment": false
  }'

Available scenarios:

matched
amount-mismatch
currency-mismatch
missing-ledger-entry
missing-payment
duplicate-payment
out-of-order-match

The engine exposes health and metrics on port 8081:

curl http://localhost:8081/actuator/health
curl http://localhost:8081/actuator/metrics/ledgerguard.reconciliations
curl http://localhost:8081/actuator/prometheus

Correctness choices

LedgerGuard uses event timestamps rather than wall-clock processing time. Payments and ledger entries are joined within a 10-second window with a 3-second grace period. This lets records reconcile even when they arrive in the opposite order.

Duplicate IDs are retained in persistent window stores for 24 hours. This bounds state growth while covering a realistic upstream retry horizon. Both decisions are documented in docs/adr.

Container deployment

The repository includes separate production Dockerfiles for the demo API and reconciliation engine, plus compose.deploy.yml for the complete three-service stack. Only the demo API should receive a public HTTP domain; Kafka and the stream engine communicate over the host's private network.

For Railway, point the two Java services at their matching railway.*.json files and set KAFKA_BOOTSTRAP_SERVERS to the Kafka service's private address. Both applications honor the platform-provided PORT value and expose readiness health checks.

Test strategy

  • Domain tests exercise every reconciliation classification.
  • TopologyTestDriver tests the real Kafka Streams topology without mocks.
  • Simulator tests verify the exact event patterns produced by demo scenarios.
  • Dashboard asset tests protect the browser console's critical controls and API integration points.
  • A Spring context test verifies production constructor wiring.
  • A Testcontainers smoke test validates compatibility with the official Apache Kafka image when Docker is available.

Run all checks with:

./mvnw verify

Roadmap

  • PostgreSQL-backed query projection and audit history
  • OpenTelemetry traces across publishing and reconciliation
  • Schema evolution and compatibility checks
  • Controlled replay from historical offsets

License

MIT License. Copyright 2026 Steve Armstrong.

About

Real-time financial transaction reconciliation with Java, Spring Boot, and Kafka Streams

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages