Skip to content

Compound Interest Calculator — Java source

Project investment growth with any compounding frequency, scheduled contributions and inflation adjustment. Yearly breakdown, growth curve and CSV export — runs entirely in your browser.

This is the Java implementation — the same logic the interactive tool runs, in a shareable, citable form.

// compound-interest-calculator — compound growth projection.
//
// Language: Java (Java 17, standard library only)
// Source:   CosmoDev polyglot showcase port of the Compound Interest
//           Calculator tool, ported from src/lib/interest.ts (the canonical
//           TypeScript implementation).
// Tool:     https://dev.cosmolabs.org/tools/compound-interest-calculator
// License:  display source — part of CosmoDev's polyglot tool pages.
//
// Display snippet: ports the core project() math from the TypeScript lib.
// The full projection (contributions, inflation, per-year rows) lives in
// the TypeScript/Go sources.
//
// Semantics: monthly simulation with the monthly rate set to the exact
// equivalent of the stated compounding frequency — (1 + r/n)^(n/12) - 1 —
// so the effective annual rate is preserved for every supported frequency.
// Returns the final balance rounded to 2 decimals, or OptionalDouble.empty()
// on invalid input (negative principal, negative years, unsupported
// frequency, non-finite values).

import java.util.OptionalDouble;
import java.util.Set;

public final class CompoundInterestCalculator {

    private static final Set<Integer> VALID_COMPOUNDS = Set.of(1, 2, 4, 12, 365);

    private CompoundInterestCalculator() {}

    /** 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 project(double principal, double ratePct, int years, int compoundsPerYear) {
        if (!Double.isFinite(principal) || !Double.isFinite(ratePct)) {
            return OptionalDouble.empty();
        }
        if (principal < 0.0 || years < 0 || !VALID_COMPOUNDS.contains(compoundsPerYear)) {
            return OptionalDouble.empty();
        }

        double n = compoundsPerYear;
        double monthlyRate = Math.pow(1.0 + ratePct / 100.0 / n, n / 12.0) - 1.0;

        double balance = principal;
        for (int i = 0; i < years * 12; i++) {
            balance *= 1.0 + monthlyRate;
        }
        return OptionalDouble.of(round2(balance));
    }

    public static void main(String[] args) {
        // Example:
        //   project(10000, 5, 10, 12) -> 16470.09
        OptionalDouble result = project(10000.0, 5.0, 10, 12);
        if (result.isEmpty() || result.getAsDouble() != 16470.09) {
            throw new AssertionError("expected 16470.09, got " + result);
        }

        // Invalid inputs are rejected, never clamped.
        if (project(-1.0, 5.0, 10, 12).isPresent()) throw new AssertionError("negative principal");
        if (project(10000.0, 5.0, -1, 12).isPresent()) throw new AssertionError("negative years");
        if (project(10000.0, 5.0, 10, 6).isPresent()) throw new AssertionError("unsupported frequency");
        if (project(Double.POSITIVE_INFINITY, 5.0, 10, 12).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 →