Skip to content

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 →