skip to content

How do you format currency correctly across locales, and how do you customize symbols and separators?

level: middleimportance: should knowfreq 40%

answer

  1. getCurrencyInstance(locale) -> symbol + placement + fraction digits
  2. locale also picks the currency; override with setCurrency
  3. Currency.getDefaultFractionDigits (USD=2, JPY=0)
  4. DecimalFormatSymbols customizes glyphs
  5. ¤ is the currency placeholder in a pattern

basics

~10 s

Use NumberFormat.getCurrencyInstance(locale) to format money; it adds the right currency symbol and decimal places for that region. To override which currency or which symbols are used, call setCurrency(Currency) or supply a custom DecimalFormatSymbols.

solid answer

~40 s

NumberFormat.getCurrencyInstance(locale) returns a formatter that applies the locale's currency symbol, symbol placement, grouping, and the standard number of fraction digits for that currency - for example getCurrencyInstance(Locale.US).format(1234.5) gives "$1,234.50" while Locale.GERMANY gives "1.234,50 EUR-symbol". A subtlety: the locale sets both the symbol AND the default currency, so a German locale assumes euros. To format an amount in a specific currency regardless of locale, cast to DecimalFormat and call setCurrency(Currency.getInstance("USD")). To customize the actual separator/symbol glyphs, build a DecimalFormatSymbols, mutate it (setGroupingSeparator, setDecimalSeparator, setCurrencySymbol), and pass it to a DecimalFormat. Currency itself carries the ISO 4217 code and getDefaultFractionDigits (2 for USD/EUR, 0 for JPY). For correctness use BigDecimal amounts and pair the formatter with an explicit Locale so output is deterministic.

code

java · 15 lines
java
NumberFormat us = NumberFormat.getCurrencyInstance(Locale.US);
us.format(1234.5); // "$1,234.50"

NumberFormat jp = NumberFormat.getCurrencyInstance(Locale.JAPAN);
jp.format(1234.5); // yen symbol + "1,235"  (0 fraction digits)

// Same German number layout, but force USD
NumberFormat de = NumberFormat.getCurrencyInstance(Locale.GERMANY);
de.setCurrency(Currency.getInstance("USD"));

// Custom glyphs via DecimalFormatSymbols
DecimalFormatSymbols sym = new DecimalFormatSymbols(Locale.US);
sym.setDecimalSeparator(',');
sym.setGroupingSeparator(' ');
DecimalFormat df = new DecimalFormat("¤#,##0.00", sym);

go deeper

for a junior

Uses getCurrencyInstance(locale) to format money instead of string-concatenating a symbol.

for a middle

Knows the locale picks both symbol and currency, can override with setCurrency, and knows fraction digits vary by currency.

for a senior

Customizes glyphs via DecimalFormatSymbols, uses the ¤ placeholder and Currency.getDefaultFractionDigits, and pairs with BigDecimal + explicit Locale.

for a principal

Defines a money-formatting/representation standard (BigDecimal amounts, currency carried alongside, deterministic locales) and weighs JDK formatting vs ICU4J for full i18n coverage.

## The currency formatting problem Money is the hardest formatting case because three things vary at once: the **symbol** ($, EUR, GBP), its **placement** (before vs after the number, with or without a space), and the **number of fraction digits** (2 for dollars/euros, 0 for yen, 3 for some dinars). A naive `"$" + amount` gets all three wrong outside the US. ## The high-level tool: getCurrencyInstance `NumberFormat.getCurrencyInstance(Locale)` returns a formatter pre-loaded with the locale's currency rules: ```java NumberFormat us = NumberFormat.getCurrencyInstance(Locale.US); us.format(1234.5); // "$1,234.50" NumberFormat jp = NumberFormat.getCurrencyInstance(Locale.JAPAN); jp.format(1234.5); // "¥1,235" (yen, 0 fraction digits, rounded) ``` Notice Japan shows **no decimals** automatically — the formatter knew yen uses zero fraction digits. ## Locale picks the currency too A crucial subtlety: the locale determines *which currency* is assumed, not just how to draw it. `getCurrencyInstance(Locale.GERMANY)` formats in **euros**. If your amount is actually US dollars but you want German *number* conventions, you must override the currency: ```java NumberFormat f = NumberFormat.getCurrencyInstance(Locale.GERMANY); f.setCurrency(Currency.getInstance("USD")); f.format(1234.5); // German layout but with the USD symbol/code ``` ## The Currency class `java.util.Currency` represents an ISO 4217 currency (e.g. "USD", "EUR", "JPY"). Useful methods: - `Currency.getInstance("USD")` — look up by code. - `getDefaultFractionDigits()` — 2 for USD/EUR, 0 for JPY, -1 for pseudo-currencies. - `getSymbol(locale)` — the symbol as that locale draws it. ## Customizing glyphs with DecimalFormatSymbols When you need full control over the literal characters, use `DecimalFormatSymbols`. It holds the actual glyphs a `DecimalFormat` uses: ```java DecimalFormatSymbols sym = new DecimalFormatSymbols(Locale.US); sym.setGroupingSeparator(' '); // use a space for thousands sym.setDecimalSeparator(','); // comma decimal sym.setCurrencySymbol("CHF "); DecimalFormat df = new DecimalFormat("¤#,##0.00", sym); // ¤ is the currency placeholder df.format(1234.5); // "CHF 1 234,50" ``` The `¤` (currency sign) in a pattern is a placeholder that gets replaced by the currency symbol from the symbols/Currency. ## Correctness rules for money 1. Use `BigDecimal` for the amount (exact decimals, no float drift). 2. Always pass an explicit `Locale` so output is deterministic across machines. 3. Set the `Currency` explicitly when the amount's currency differs from the locale's assumption. 4. Trust `getDefaultFractionDigits()` rather than hardcoding 2 — yen and others differ. 5. Remember these formatters are not thread-safe (covered elsewhere). ## Key takeaways 1. `getCurrencyInstance(locale)` handles symbol, placement, grouping, and fraction digits. 2. The locale also chooses the assumed currency — override with `setCurrency`. 3. `Currency` carries the ISO code and the canonical fraction-digit count. 4. `DecimalFormatSymbols` lets you customize the literal separator/symbol glyphs; `¤` is the currency placeholder in a pattern.

  • Why does getCurrencyInstance(Locale.JAPAN).format(1234.5) drop the decimals?
    The yen's default fraction-digit count is 0, so the currency formatter rounds to a whole number automatically. The formatter reads this from the Currency, not from your pattern.
  • How do you format a USD amount but with German number conventions?
    Create getCurrencyInstance(Locale.GERMANY) for the layout, then call setCurrency(Currency.getInstance("USD")) to override the assumed currency.

saying these in an interview costs you the question

  • Concatenating a hardcoded $ instead of using getCurrencyInstance
  • Hardcoding 2 decimal places (wrong for JPY and others)
  • Assuming the locale won't change the currency, only the symbol
  • Editing the pattern when you actually needed DecimalFormatSymbols

context