A Java 17 and Spring Boot 3 API that evaluates a loan application with deterministic rules, produces an indicative annual rate and amortization schedule, and stores the input and decision in an H2 file database.
This is a portfolio service, not a lending system. It has no authentication, bureau integration, affordability verification, fraud detection, regulatory workflow, or production-grade data controls. Do not use its score or price to make real credit decisions.
./mvnw spring-boot:runOn Windows PowerShell:
.\mvnw.cmd spring-boot:runRun the verification suite:
./mvnw testThe service listens on http://localhost:8080. H2 persists to ./data/loan-origination.mv.db, so decisions survive restarts on the same machine.
| Method | Path | Purpose |
|---|---|---|
POST |
/api/loans/apply |
Validate, score, price, schedule, and persist an application |
GET |
/api/loans/{applicationId} |
Retrieve a persisted decision and regenerate its deterministic schedule |
POST |
/api/loans/{applicationId}/decide |
Resolve a MANUAL_REVIEW application to APPROVED or REJECTED |
GET |
/api/loans/health |
Return service health |
Apply:
curl -X POST http://localhost:8080/api/loans/apply \
-H "Content-Type: application/json" \
-d '{
"applicantName": "Ada Lovelace",
"monthlyIncome": 40000,
"monthlyDebt": 3000,
"requestedAmount": 60000,
"termMonths": 24,
"latePaymentsLast12m": 0
}'PowerShell uses curl.exe, avoiding the curl alias:
curl.exe -X POST http://localhost:8080/api/loans/apply -H "Content-Type: application/json" -d '{\"applicantName\":\"Ada Lovelace\",\"monthlyIncome\":40000,\"monthlyDebt\":3000,\"requestedAmount\":60000,\"termMonths\":24,\"latePaymentsLast12m\":0}'Resolve a review after replacing the placeholder with the returned identifier:
curl -X POST http://localhost:8080/api/loans/LN-EXAMPLE/decide \
-H "Content-Type: application/json" \
-d '{"decision":"APPROVED"}'Every application begins at 650 points. The scoring service then applies the following fixed adjustments:
| Rule | Adjustment |
|---|---|
| Debt-to-income at or below 20% | +80 |
| Debt-to-income above 20% through 40% | +20 |
| Debt-to-income above 40% | -90 |
| Requested amount at or below three monthly incomes | +30 |
| Requested amount above six monthly incomes | -60 |
| No late payments in the last 12 months | +40 |
| One or two late payments | -30 |
| Three or more late payments | -100 |
| Term longer than 60 months | -20 |
Scores are constrained to 300 through 850. Scores of 700 or more are APPROVED; 580 through 699 are MANUAL_REVIEW; lower scores are REJECTED.
The annual percentage rate is a score band, not a quote or a regulatory APR calculation:
| Score | Annual rate |
|---|---|
| 760–850 | 8% |
| 700–759 | 12% |
| 660–699 | 18% |
| 580–659 | 28% |
| Below 580 | Rejected, no rate |
For non-rejected applications, monthly payments use the annuity formula P × r × (1 + r)^n / ((1 + r)^n - 1), where r is the annual rate divided by 12. Monetary values use RoundingMode.HALF_UP at two decimal places. Each schedule row satisfies payment = principal + interest; the final period uses the remaining principal, so total principal equals the requested amount within 0.01 and the final balance is zero. A manual-review result is indicative until it is resolved.
The response and application snapshot include a loan-score-v1 score breakdown. Each applied rule has a stable rule ID, its score delta, and the input values used to evaluate it. The snapshot also has createdAt and, once resolved, decidedAt.
The database stores append-only APPLICATION_SCORED and MANUAL_DECIDED audit events with their timestamps and resulting decision. The decision snapshot remains the current-state record. Manual resolution is transactional and uses optimistic locking, so an already-resolved or concurrent decision returns a conflict instead of silently replacing the prior result.
Requests use Bean Validation. API responses include Cache-Control: no-store, Content-Security-Policy: default-src 'none', Referrer-Policy: no-referrer, X-Content-Type-Options: nosniff, and X-Frame-Options: DENY. These headers do not replace authentication, TLS, authorization, rate limiting, encryption, retention controls, or a security review.
- RetailBankingCore - JWT accounts and double-entry ledger
- CardLifecycleApi - card issue, daily limit, authorize
- SwiftMt103Parser - MT103 field extraction