FINERACT-2789: Balloon loan not working as expected - #6352
Open
mariowise wants to merge 1 commit into
Open
Conversation
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
force-pushed
the
community/fix-grace-on-principal
branch
from
August 29, 2026 13:34
cd67791 to
b3db9d3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-1as 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 calledapplyPrincipalMoratoriumIfRequired(...)that does two things: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
principalPaymentGraceflag that sits next to the existinginterestPaymentGraceto 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 buildJavaSdkIt has also been manually tested seeing the same correct behavior between N-2 and N-1 grace on principal.
Recommended reviewers