Skip to content

FINERACT-2789: Balloon loan not working as expected - #6352

Open
mariowise wants to merge 1 commit into
apache:developfrom
mariowise:community/fix-grace-on-principal
Open

FINERACT-2789: Balloon loan not working as expected#6352
mariowise wants to merge 1 commit into
apache:developfrom
mariowise:community/fix-grace-on-principal

Conversation

@mariowise

Copy link
Copy Markdown

Summary

When creating a loan product that uses progressive schedule, declining balance interest method and has a grace period on principal. The expected behavior is that during the grace period, no principal is applied to the grace period tranches. This currently works in the system for grace periods within the range {1..N-2}. Where N is the number of repayments.

When you setup N-1 as the grace period, the system behaves with the bug, and all tranches have principal on them. The "Bullet/Balloon" loan concept (where the entire principal is repaid fully at the last repayment) is not possible to achieve.

Root cause analysis

The code lives in ProgressiveEMICalculator.java. There's a method called applyPrincipalMoratoriumIfRequired(...) that does two things:

  1. Marks the first N grace installments as interest-only (correct so far).
  2. Then it re-runs the amortization math on only the leftover installments — the ones that still owe principal. It does this by slicing the list: subList(gracePeriods, size).

Where it breaks

After the moratorium correctly parks the full principal onto that single last installment, a later normalization step kicks in (getEmiAdjustment, checkAndAdjustEmiIfNeededOnRelatedRepaymentPeriods, calculateLastUnpaidRepaymentPeriodEMI). This step's job is normally to fine-tune the last installment so the numbers reconcile perfectly (rounding, etc.).

But when there's only one non-grace period, this step re-does the amortization from scratch and undoes the moratorium's work. It ends up spreading the principal back across installments.

Why N-2 works but N-1 doesn't?

When grace is N - 2, the leftover slice has two installments to work with. With two or more periods, the equal-installment solver has "room" to land on a valid split, and the moratorium survives the cleanup step. It's only the single-remaining-period edge case (N - 1) that trips the bug — which is precisely why the reproduction shows "N-2 works, N-1 breaks." The defect lives entirely at that one-period boundary.

Steps to reproduce

Please see the jira issue at FINERACT-2789.

Proposed solution

Adding a new principalPaymentGrace flag that sits next to the existing interestPaymentGrace to mark the entries as part of principal grace/moratorium. And avoiding the normalizations steps from breaking the 0 principal rule setup on the grace period setting.

How is this change tested?

3 new unit tests have been added: a control one to verify N-2 works as it is today (grace period on principal), and 2 more to verify N-1 with two loan lengths. Also, the following command is passing running the whole suite of unit tests.

./gradlew test \
  -x :twofactor-tests:test \
  -x :oauth2-tests:test \
  -x :integration-tests:test \
  -x :fineract-client:test \
  -x :fineract-client-feign:test \
  -x :fineract-e2e-tests-core:test \
  -x :fineract-e2e-tests-runner:test \
  -x buildJavaSdk

It has also been manually tested seeing the same correct behavior between N-2 and N-1 grace on principal.

Recommended reviewers

For progressive, declining balance loans with grace period on principal the EMI calculation process
works as expected for N-2 tranches, but breaks at the N-1 where all tranches have principal on them.
This makes impossible to have Balloon loans (where all principal is repaid at the last tranche)

https://issues.apache.org/jira/browse/FINERACT-2789
@mariowise
mariowise force-pushed the community/fix-grace-on-principal branch from cd67791 to b3db9d3 Compare August 29, 2026 13:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant