Mortgage Calculator — Java source
Compute monthly payments, the full amortization schedule and total interest, with extra-payment what-if analysis showing months and interest saved — runs entirely in your browser.
This is the Java implementation — the same logic the interactive tool runs, in a shareable, citable form.
// mortgage-calculator — amortized monthly payment.
//
// Language: Java (Java 17, standard library only)
// Source: CosmoDev polyglot showcase port of the Mortgage Calculator
// tool, ported from src/lib/mortgage.ts (the canonical
// TypeScript implementation).
// Tool: https://dev.cosmolabs.org/tools/mortgage-calculator
// License: display source — part of CosmoDev's polyglot tool pages.
//
// Display snippet: ports the core monthlyPayment() formula from the
// TypeScript lib. The full amortization schedule (integer-cent tracking,
// extra payments, payoff dates) lives in the TypeScript/Go sources.
//
// Semantics: standard amortization formula P*r*(1+r)^n / ((1+r)^n - 1)
// with r = ratePct/100/12 and n = years*12. A zero rate divides the
// principal evenly across n months. Returns the payment rounded to
// cents, or OptionalDouble.empty() on invalid input (non-positive
// principal, non-positive years, non-finite values).
import java.util.OptionalDouble;
public final class MortgageCalculator {
private MortgageCalculator() {}
/** Round to 2 decimals, half-up (mirrors the TS Math.round). */
static double round2(double n) {
return Math.floor(n * 100.0 + 0.5) / 100.0;
}
public static OptionalDouble monthlyPayment(double principal, double ratePct, int years) {
if (!Double.isFinite(principal) || !Double.isFinite(ratePct)) {
return OptionalDouble.empty();
}
if (principal <= 0.0 || years <= 0) {
return OptionalDouble.empty();
}
double r = ratePct / 100.0 / 12.0;
double n = years * 12.0;
double payment;
if (r == 0.0) {
payment = principal / n;
} else {
double growth = Math.pow(1.0 + r, n);
payment = principal * r * growth / (growth - 1.0);
}
return OptionalDouble.of(round2(payment));
}
public static void main(String[] args) {
// Example:
// monthlyPayment(300000, 6, 30) -> 1798.65
OptionalDouble result = monthlyPayment(300000.0, 6.0, 30);
if (result.isEmpty() || result.getAsDouble() != 1798.65) {
throw new AssertionError("expected 1798.65, got " + result);
}
// A zero rate divides the principal evenly.
OptionalDouble zeroRate = monthlyPayment(300000.0, 0.0, 30);
if (zeroRate.isEmpty() || zeroRate.getAsDouble() != 833.33) {
throw new AssertionError("expected 833.33 at a zero rate");
}
// Invalid inputs are rejected, never clamped.
if (monthlyPayment(-1.0, 6.0, 30).isPresent()) throw new AssertionError("non-positive principal");
if (monthlyPayment(300000.0, 6.0, 0).isPresent()) throw new AssertionError("non-positive years");
if (monthlyPayment(Double.POSITIVE_INFINITY, 6.0, 30).isPresent()) throw new AssertionError("non-finite value");
System.out.println("ok");
}
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →