Skip to content

pauluswi/tubingen

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

19 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

TΓΌbingen – Reconciliation Engine

Java CI with Maven codecov License: MIT Language Framework

TΓΌbingen – Reconciliation Engine is a backend-focused banking reconciliation service built with Java Spring Boot, designed to match and reconcile financial transactions between two independent sources (e.g. Core Banking vs Switch / Payment Provider).

The system demonstrates real-world banking reconciliation logic, using an efficient Two-Pointer algorithm to achieve linear-time matching on large, ordered transaction datasets.

This project is intentionally designed as a clean, production-style backend service, suitable for banking, fintech, and payment systems.


🎯 Purpose & Motivation

In real banking systems, transactions are processed by multiple parties:

  • Core Banking System
  • Payment Switch (ATM, EDC, QR, ISO8583)
  • External Clearing or Settlement Providers

Due to network delays, retries, duplicates, or partial failures, transaction records between systems often do not perfectly align.

Reconciliation ensures:

  • Financial consistency
  • Accurate settlement
  • Early detection of missing, duplicate, or mismatched transactions

This project simulates that exact scenario in a simplified but realistic way.


🧠 Key Features

  • Reconcile transactions between Source A and Source B
  • Efficient Two-Pointer matching algorithm (O(n))
  • Tolerance-based matching (timestamp & amount)
  • Detection of:
    • Matched transactions
    • Missing transactions
    • Amount mismatches
    • Duplicates
  • Batch-style reconciliation jobs
  • RESTful APIs for ingestion, execution, and reporting
  • Backend-first design (frontend optional)

πŸ— High-Level Architecture

            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
            β”‚        Client          β”‚
            β”‚  (curl / Postman / UI) β”‚
            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚ REST
                        β–Ό
            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
            β”‚   Spring Boot API      β”‚
            β”‚  Tubingen Recon Engine   β”‚
            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       β”‚                       β”‚
β–Ό                       β–Ό                       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Ingest       β”‚ β”‚ Ingest       β”‚ β”‚ Ingest       β”‚
β”‚ Service      β”‚ β”‚ Reconcil     β”‚ β”‚ Reconcil     β”‚
β”‚ (CSV / Logs) β”‚ β”‚ (Two Ptrs)   β”‚ β”‚ (CSV / Logs) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚                       β”‚                       β”‚
β–Ό                       β–Ό                       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PostgreSQL / H2                                β”‚
β”‚ transaction_record | recon_job |               β”‚
β”‚ recon_match | audit                            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

🧩 Core Domain Model

TransactionRecord

Represents a normalized transaction from any source.

Key attributes:

  • Source (A or B)
  • Transaction ID / STAN
  • Amount & currency
  • Timestamp (UTC)
  • Raw payload (for audit)

Reconciliation Job

Represents a single reconciliation execution between two datasets.

Reconciliation Match

Stores the outcome of matching:

  • MATCHED
  • MISSING_IN_SOURCE_A
  • MISSING_IN_SOURCE_B
  • AMOUNT_MISMATCH
  • DUPLICATE

βš™οΈ Reconciliation Algorithm (Hybrid Approach)

The engine uses a Hybrid Approach combining HashMap indexing and Two-Pointer technique.

1. Duplicate Detection (Pre-processing)

Before matching, each source list is scanned for duplicate Transaction IDs.

  • If a duplicate ID is found within the same source, it is immediately marked as DUPLICATE.
  • These records are removed from the matching pool to prevent false positives.

2. Exact ID Matching (Pass 1)

  • Source B is indexed into a HashMap for O(1) lookup.
  • Source A is iterated to find exact ID matches in the map.
  • Matches are recorded as MATCHED or AMOUNT_MISMATCH.
  • Unmatched records are saved for the next pass.

3. Fuzzy Time Matching (Pass 2 - Two Pointers)

  • Remaining records from both sources are sorted by timestamp.
  • A Two-Pointer algorithm traverses both lists to find matches based on:
    • Time proximity (within configurable tolerance)
    • Amount equality (within configurable tolerance)
  • Records that remain unmatched are marked as MISSING_IN_SOURCE_A or MISSING_IN_SOURCE_B.

This approach ensures that exact ID matches are prioritized over fuzzy matches, which is critical for accuracy in banking systems.


πŸ”Œ API Overview

Ingest Transactions

Upload transactions for a specific source.

  • Endpoint: POST /api/ingest
  • Body: List<TransactionRecord>

Start Reconciliation

Creates and runs a reconciliation job with configurable tolerances.

  • Endpoint: POST /api/reconcile/start
  • Body: ReconciliationRequest (specifies job parameters)

Get Job Status

Retrieves the status and summary of a reconciliation job.

  • Endpoint: GET /api/recon/{jobId}/status

Get Job Results

Retrieves the detailed results (matches, mismatches, missing) for a job.

  • Endpoint: GET /api/recon/{jobId}/results

πŸš€ How to Use

1. Ingest Transactions from Source A

curl -X POST http://localhost:8080/api/ingest \
-H "Content-Type: application/json" \
-d '[
  {
    "source": "A",
    "transactionId": "TXN1001",
    "amount": 150.00,
    "timestamp": "2023-10-27T10:00:00Z"
  },
  {
    "source": "A",
    "transactionId": "TXN1002",
    "amount": 200.50,
    "timestamp": "2023-10-27T10:05:00Z"
  }
]'

2. Ingest Transactions from Source B

curl -X POST http://localhost:8080/api/ingest \
-H "Content-Type: application/json" \
-d '[
  {
    "source": "B",
    "transactionId": "TXN1001",
    "amount": 150.00,
    "timestamp": "2025-10-27T10:00:05Z"
  },
  {
    "source": "B",
    "transactionId": "TXN1003",
    "amount": 300.00,
    "timestamp": "2025-10-27T10:10:00Z"
  }
]'

3. Start Reconciliation

This triggers the two-pointer algorithm.

curl -X POST http://localhost:8080/api/reconcile/start \
-H "Content-Type: application/json" \
-d '{
  "jobName": "Daily Reconciliation",
  "sourceA": "A",
  "sourceB": "B",
  "timestampToleranceMillis": 10000,
  "amountTolerance": 0.01
}'

This will return a jobId. Let's assume it's 123e4567-e89b-12d3-a456-426614174000.

4. Check Job Status

curl http://localhost:8080/api/recon/123e4567-e89b-12d3-a456-426614174000/status

Expected Response:

{
  "jobId": "123e4567-e89b-12d3-a456-426614174000",
  "status": "COMPLETED",
  "summary": {
    "matched": 1,
    "missingInSourceB": 1,
    "missingInSourceA": 1,
    "amountMismatch": 0,
    "duplicates": 0
  }
}

5. Get Detailed Results

curl http://localhost:8080/api/recon/123e4567-e89b-12d3-a456-426614174000/results

Expected Response:

[
  {
    "type": "MATCHED",
    "transactionA": { "transactionId": "TXN1001", "amount": 150.00},
    "transactionB": { "transactionId": "TXN1001", "amount": 150.00}
  },
  {
    "type": "MISSING_IN_SOURCE_B",
    "transactionA": { "transactionId": "TXN1002"},
    "transactionB": null
  },
  {
    "type": "MISSING_IN_SOURCE_A",
    "transactionA": null,
    "transactionB": { "transactionId": "TXN1003"}
  }
]

πŸ›  Technology Stack

  • Java 17+
  • Spring Boot
  • Spring Web
  • Spring Data JPA
  • H2 (local) / PostgreSQL (production)
  • Maven
  • Docker (optional)

πŸ“ Project Structure

tubingen-reconciliation-engine/

β”œβ”€β”€ src/main/java/com/pswied/tubingen/
    β”‚   β”œβ”€β”€ controller/
    β”‚   β”œβ”€β”€ service/
    β”‚   β”œβ”€β”€ model/
    β”‚   β”œβ”€β”€ repository/
    β”‚   └── config/
    β”œβ”€β”€ src/test/
    β”œβ”€β”€ sample-data/
    β”œβ”€β”€ docs/
    β”œβ”€β”€ Dockerfile
    β”œβ”€β”€ pom.xml
    └── README.md

πŸš€ Running Locally

./mvnw spring-boot:run

Access API at

http://localhost:8080

πŸ” Security & Production Considerations

  • Designed to integrate with JWT / OAuth2
  • Input validation & file size limits
  • Audit logging for traceability
  • Stateless API suitable for horizontal scaling

🧭 Roadmap

  • CSV upload support (Source A / Source B)
  • Async job execution
  • React dashboard (optional)
  • ISO8583 / ISO20022 mock adapters
  • Kubernetes deployment example
  • Metrics & monitoring (Actuator + Prometheus)

πŸ‘€ Author

Slamet Widodo (Wied)
Software Engineer / Engineering Manager
Banking Β· Middleware Β· Distributed Systems

πŸ“„ License

This project is provided for educational and portfolio purposes.

About

backend-focused banking reconciliation service

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages