skip to content

NumberFormat & DecimalFormat

NumberFormat and DecimalFormat handle locale-aware number, currency and percent output with pattern control over grouping and rounding. The detail worth knowing is that they are not thread-safe, so a shared static instance is a bug.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

How do you format a number for display in Java using NumberFormat, and why is the Locale important?

level: juniorimportance: must knowfreq 62%

answer

  1. Abstract factory: getInstance / getCurrency / getPercent / getInteger
  2. Locale controls separators, currency symbol, percent sign
  3. Default locale = JVM/OS dependent = bug risk
  4. format() -> String, parse() -> Number
  5. getPercentInstance multiplies by 100

basics

~20 s

Use NumberFormat factory methods like NumberFormat.getInstance(locale), then call format(value). The Locale decides things like whether the decimal point is a dot or a comma and which grouping separator is used, so the output looks right for that region.

solid answer

~40 s

NumberFormat is an abstract factory for locale-aware number formatting and parsing. You never construct it directly; you call static factories: getInstance / getNumberInstance for general numbers, getCurrencyInstance for money, getPercentInstance for percentages, and getIntegerInstance for whole numbers. Each takes an optional Locale; without one it uses the default locale, which is risky for servers. The Locale drives the decimal separator (dot vs comma), the grouping separator, currency symbol, and digit shapes. For example 1234.5 formats as "1,234.5" in US English but "1.234,5" in German. format() returns a String; parse() turns a localized String back into a Number. Because the same value renders differently per region, always pass an explicit Locale rather than relying on the JVM default, which can change between environments.

go deeper

for a junior

Knows to call NumberFormat.getInstance and format(), and that a Locale affects the output.

for a middle

Picks the right factory per use case and always passes an explicit Locale; knows percent multiplies by 100.

for a senior

Explains the default-locale hazard, separator differences across regions, and parse() round-tripping; reasons about which concrete type is returned.

for a principal

Sets formatting policy across a codebase (e.g. inject Locale, ban default-locale calls via lint), weighs NumberFormat vs ICU4J / java.time for broader i18n needs.

## What problem this solves A raw number like `1234567.89` is just a value in memory. To *show* it to a human you must turn it into text, and the conventions for that text differ by region (called a **Locale**). In the United States that number reads `1,234,567.89`; in Germany it reads `1.234.567,89`; in France `1 234 567,89`. The character used to separate thousands (the **grouping separator**) and the character before the fractional part (the **decimal separator**) both change. `NumberFormat` is the Java Standard Library class that encapsulates these rules. ## What NumberFormat is `java.text.NumberFormat` is an **abstract class** (you cannot do `new NumberFormat()`). It is also a **factory**: it exposes static methods that return a ready-configured concrete instance (usually a `DecimalFormat` under the hood). The main factories are: - `getInstance()` / `getNumberInstance()` — general-purpose number formatting. - `getIntegerInstance()` — rounds to a whole number. - `getCurrencyInstance()` — adds the currency symbol and uses the currency's fraction-digit count. - `getPercentInstance()` — multiplies by 100 and appends `%`. Each has a no-arg overload (uses `Locale.getDefault()`) and an overload taking an explicit `Locale`. ## What a Locale is A **Locale** is an object identifying a language/region, e.g. `Locale.US`, `Locale.GERMANY`, or `Locale.forLanguageTag("fr-FR")`. It carries the formatting conventions: separators, currency symbol, percent sign, and digit set. The **default locale** is whatever the JVM picked from the operating system at startup — which means the *same code can produce different output on your laptop and on a production server*. That is the single most common bug here. ## The two operations - `String format(double or long)` — value to localized text. - `Number parse(String)` — localized text back to a number (throws `ParseException` on bad input). ## Worked example ```java double v = 1234.5; NumberFormat us = NumberFormat.getInstance(Locale.US); NumberFormat de = NumberFormat.getInstance(Locale.GERMANY); us.format(v); // "1,234.5" de.format(v); // "1.234,5" ``` ## Key takeaways 1. Get instances from factory methods, never via constructor. 2. **Always pass an explicit Locale** in server/library code; relying on the default is non-deterministic across machines. 3. The Locale, not your code, decides separators, currency symbols, and percent signs — that is the whole point of the class. 4. For money use `getCurrencyInstance`, for percentages use `getPercentInstance`; they apply the right rules (and the percent one multiplies by 100, a classic surprise).

  • What happens if you call NumberFormat.getInstance() with no Locale on a server?
    It uses Locale.getDefault(), which is derived from the host OS at JVM startup. Output can silently differ between dev and prod, so explicit Locales are preferred.
  • Which factory would you use to show 0.075 as 7.5%?
    NumberFormat.getPercentInstance(locale); it multiplies by 100 and appends the locale's percent sign. You may need to set minimum/maximum fraction digits to keep the .5.

saying these in an interview costs you the question

  • Thinking you can call new NumberFormat() (it is abstract)
  • Relying on the default Locale in server code so output differs per machine
  • Forgetting getPercentInstance multiplies the value by 100
  • Assuming the decimal separator is always a dot

context

open as a page

What is DecimalFormat and how do its pattern symbols (0, #, comma, dot) control the output?

level: middleimportance: must knowfreq 58%

basics

~20 s

DecimalFormat is a concrete NumberFormat that formats numbers from a pattern string. In the pattern, 0 forces a digit (shows zero if absent), # shows a digit only if present, the comma marks grouping, and the dot marks the decimal point. So "#,##0.00" gives grouped numbers with exactly two decimals.

open as a page

Are NumberFormat and DecimalFormat thread-safe, and how should you use them in concurrent code?

level: seniorimportance: must knowfreq 52%

basics

~20 s

No. NumberFormat and DecimalFormat are not thread-safe. If multiple threads share one instance and call format or parse at the same time, you can get wrong results or exceptions. Create a new instance per use, or give each thread its own via ThreadLocal.

open as a page

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

level: middleimportance: should knowfreq 40%

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.

open as a page

How does rounding work in NumberFormat/DecimalFormat, and what is the default rounding behavior?

level: seniorimportance: should knowfreq 44%

basics

~20 s

When a number has more decimals than the format shows, DecimalFormat rounds it. By default it uses HALF_EVEN (banker's rounding), so 2.5 rounds to 2 and 3.5 rounds to 4. You can change this with setRoundingMode, e.g. to HALF_UP.

open as a page