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 method | What 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
NumberFormatrounds 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
| Symbol | Meaning |
|---|---|
0 | Digit, shows 0 if absent |
# | Digit, shows nothing if absent |
. | Decimal separator |
, | Grouping separator |
% | Multiplies by 100 and appends % |
E | Scientific 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
DecimalFormatSymbolsin 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 checkedParseException. 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:
- Create a new instance per call — cheap for most use cases.
- 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
| Goal | API |
|---|---|
| Locale-aware number | NumberFormat.getInstance(locale) |
| Locale-aware currency | NumberFormat.getCurrencyInstance(locale) |
| Locale-aware percent | NumberFormat.getPercentInstance(locale) |
| Custom pattern | new DecimalFormat("pattern") |
| Custom pattern + locale | new DecimalFormat("pattern", new DecimalFormatSymbols(locale)) |
| Parse string → number | numberFormat.parse(str) |
| Currency metadata | Currency.getInstance(locale) |
| Compact numbers (Java 12+) | NumberFormat.getCompactNumberInstance(locale, style) |
Related Topics
- 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 Classes —
Integer,Double, and friends that hold the parsedNumbervalues returned byNumberFormat - 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