Skip to content
Java i18n 5 min read

Formatting Numbers & Currency

Numbers look different around the world. In the United States, one million dollars is written $1,000,000.00, but in Germany the same amount is 1.000.000,00 €, and in India it is ₹10,00,000.00. Java’s java.text.NumberFormat and java.text.DecimalFormat classes let you format and parse numbers correctly for any locale without writing a single manual string-concat.

The NumberFormat Class

NumberFormat is the high-level entry point in java.text. You ask it for a locale-aware formatter and call format(). There are three factory methods you’ll use most often:

Factory methodWhat it produces
NumberFormat.getInstance(locale)General-purpose number
NumberFormat.getCurrencyInstance(locale)Currency with symbol
NumberFormat.getPercentInstance(locale)Percentage (0.75 → 75%)
NumberFormat.getIntegerInstance(locale)Integer, no fraction digits

Formatting a Number for Different Locales

import java.text.NumberFormat;
import java.util.Locale;

public class NumberLocaleDemo {
    public static void main(String[] args) {
        double value = 1234567.89;

        NumberFormat us = NumberFormat.getInstance(Locale.US);
        NumberFormat de = NumberFormat.getInstance(Locale.GERMANY);
        NumberFormat in = NumberFormat.getInstance(new Locale("en", "IN"));

        System.out.println("US:     " + us.format(value));
        System.out.println("Germany:" + de.format(value));
        System.out.println("India:  " + in.format(value));
    }
}

Output:

US:     1,234,567.89
Germany:1.234.567,89
India:  12,34,567.89

Notice that India uses a two-digit grouping above the first three digits — Java handles this automatically when you pass the right locale.

Formatting Currency

import java.text.NumberFormat;
import java.util.Locale;

public class CurrencyDemo {
    public static void main(String[] args) {
        double price = 9999.50;

        NumberFormat usd = NumberFormat.getCurrencyInstance(Locale.US);
        NumberFormat eur = NumberFormat.getCurrencyInstance(Locale.GERMANY);
        NumberFormat jpy = NumberFormat.getCurrencyInstance(Locale.JAPAN);

        System.out.println(usd.format(price));
        System.out.println(eur.format(price));
        System.out.println(jpy.format(price));
    }
}

Output:

$9,999.50
9.999,50 €
¥10,000

Note: The Japanese yen has no fractional digits by convention, so NumberFormat rounds automatically.

Controlling Fraction Digits

You can override the default number of decimal places on any formatter:

import java.text.NumberFormat;
import java.util.Locale;

public class FractionDigitsDemo {
    public static void main(String[] args) {
        NumberFormat fmt = NumberFormat.getInstance(Locale.US);
        fmt.setMinimumFractionDigits(2);
        fmt.setMaximumFractionDigits(4);

        System.out.println(fmt.format(3.1));        // 3.10
        System.out.println(fmt.format(3.14159265)); // 3.1416 (rounded)
    }
}

Output:

3.10
3.1416

Formatting Percentages

import java.text.NumberFormat;
import java.util.Locale;

public class PercentDemo {
    public static void main(String[] args) {
        NumberFormat pct = NumberFormat.getPercentInstance(Locale.US);
        pct.setMinimumFractionDigits(1);

        System.out.println(pct.format(0.75));   // 75.0%
        System.out.println(pct.format(0.3333)); // 33.3%
    }
}

Output:

75.0%
33.3%

The DecimalFormat Class

DecimalFormat extends NumberFormat and gives you full pattern-based control. It’s perfect when you need a custom layout — engineering notation, padding, sign characters — that the locale factories don’t expose.

Pattern Symbols

SymbolMeaning
0Digit, shows 0 if absent
#Digit, shows nothing if absent
.Decimal separator
,Grouping separator
%Multiplies by 100 and appends %
EScientific notation
¤Currency symbol (locale-sensitive)

Basic Pattern Examples

import java.text.DecimalFormat;

public class DecimalFormatDemo {
    public static void main(String[] args) {
        double n = 1234.5;

        System.out.println(new DecimalFormat("#,###.00").format(n));   // 1,234.50
        System.out.println(new DecimalFormat("000000.000").format(n)); // 001234.500
        System.out.println(new DecimalFormat("0.##E0").format(n));     // 1.23E3
        System.out.println(new DecimalFormat("##.##%").format(0.876)); // 87.6%
    }
}

Output:

1,234.50
001234.500
1.23E3
87.6%

Locale-Aware DecimalFormat

By default, DecimalFormat uses the JVM’s default locale for the actual separator characters. To be explicit, pass a DecimalFormatSymbols object:

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;

public class LocaleDecimalDemo {
    public static void main(String[] args) {
        DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.GERMANY);
        DecimalFormat fmt = new DecimalFormat("#,##0.00", symbols);

        System.out.println(fmt.format(9876543.21));
    }
}

Output:

9.876.543,21

Tip: Always pass explicit DecimalFormatSymbols in library code. Relying on the default locale makes output differ between developer machines and servers.

Parsing Formatted Strings Back to Numbers

Both NumberFormat and DecimalFormat can reverse the process — turn a locale-formatted string back into a Number.

import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;

public class ParseDemo {
    public static void main(String[] args) throws ParseException {
        NumberFormat fmt = NumberFormat.getInstance(Locale.GERMANY);
        Number result = fmt.parse("1.234.567,89");
        System.out.println(result.doubleValue()); // 1234567.89
    }
}

Output:

1234567.89

Warning: parse() throws a checked ParseException. Always handle it — or use a try-catch — because a stray letter in the input will cause it to fail.

Using Currency to Inspect Currency Metadata

The java.util.Currency class lets you look up currency codes and symbols without formatting a number:

import java.util.Currency;
import java.util.Locale;

public class CurrencyMetaDemo {
    public static void main(String[] args) {
        Currency usd = Currency.getInstance(Locale.US);
        System.out.println(usd.getCurrencyCode());          // USD
        System.out.println(usd.getSymbol(Locale.US));       // $
        System.out.println(usd.getDefaultFractionDigits()); // 2

        Currency jpy = Currency.getInstance(Locale.JAPAN);
        System.out.println(jpy.getDefaultFractionDigits()); // 0
    }
}

This is useful when you need to dynamically determine how many decimal places to allow in a form field.

Under the Hood

NumberFormat.getCurrencyInstance() returns a DecimalFormat internally. The factory looks up the CLDR (Common Locale Data Repository) data bundled with the JDK to find the correct pattern string, grouping size, decimal and grouping separator characters, currency symbol, and fraction-digit count for the requested locale.

DecimalFormat.format() is not thread-safe. If you share one instance across threads you will get race conditions on the internal buffer. Two safe patterns are:

  1. Create a new instance per call — cheap for most use cases.
  2. Use ThreadLocal<DecimalFormat> — avoids allocation in hot loops.
// Thread-safe via ThreadLocal
private static final ThreadLocal<NumberFormat> CURRENCY_FMT =
    ThreadLocal.withInitial(() -> NumberFormat.getCurrencyInstance(Locale.US));

public static String formatUSD(double amount) {
    return CURRENCY_FMT.get().format(amount);
}

For the highest throughput in modern Java (Java 12+) you can also look at java.text.CompactNumberFormat, which produces short forms like 1.2M or 1.2 million — see NumberFormat.getCompactNumberInstance().

Quick Reference

GoalAPI
Locale-aware numberNumberFormat.getInstance(locale)
Locale-aware currencyNumberFormat.getCurrencyInstance(locale)
Locale-aware percentNumberFormat.getPercentInstance(locale)
Custom patternnew DecimalFormat("pattern")
Custom pattern + localenew DecimalFormat("pattern", new DecimalFormatSymbols(locale))
Parse string → numbernumberFormat.parse(str)
Currency metadataCurrency.getInstance(locale)
Compact numbers (Java 12+)NumberFormat.getCompactNumberInstance(locale, style)
  • Formatting Dates & Times — companion page for locale-aware date and time formatting using DateTimeFormatter
  • Internationalization — overview of Java’s i18n architecture, Locale, and how all the pieces fit together
  • ResourceBundle — load locale-specific labels and messages to pair with your formatted numbers
  • Wrapper ClassesInteger, Double, and friends that hold the parsed Number values returned by NumberFormat
  • String Methods — string manipulation you often apply after or before number formatting
  • Java 8 Features — the release that also brought the new Date/Time API alongside the i18n improvements
Last updated June 13, 2026
Was this helpful?