Localization ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-10-17

Java NumberFormat Class: Formatting Numbers, Currency and Percentages

A classic bug: NumberFormat.getCurrencyInstance(Locale.FRENCH).format(123.4567). The expected result was euros, but the screen showed 123,46 ¤. There was no exception: a locale without a country has no currency, so Java prints a generic currency sign. Why this happens and how to fix it is shown in the currency section.

The NumberFormat class in Java (java.text.NumberFormat) is an abstract class that formats numbers, currency amounts and percentages according to the rules of a given locale, and parses such strings back into numbers. It knows which decimal separator to use (dot or comma), the grouping separator, the currency symbol and the number of fraction digits.

You do not create it with new. You call a factory method such as NumberFormat.getInstance(locale). What a locale is and how to create one is covered in the lesson Java Locale Class, and why an application needs it at all is explained in Java Internationalization and Localization. In a localized application the texts usually come from a ResourceBundle, while numbers, prices and percentages are formatted with NumberFormat.

Creating a NumberFormat: getInstance and other factory methods

You get a NumberFormat object from static factory methods: getInstance() for plain numbers, getCurrencyInstance() for money, getPercentInstance() for percentages. Each method comes in two variants: without arguments (the default locale is used) and with a Locale parameter. Every call returns a new number formatter that you can configure further.

Method What it formats Default fraction digits Example for Locale.US
getInstance(), getNumberInstance() A plain number (the two methods are equivalent) 0 to 3 1234.5678 → 1,234.568
getIntegerInstance() An integer, the fraction is rounded 0 1234.5678 → 1,235
getCurrencyInstance() A money amount with a currency symbol Depends on the currency (2 for USD and EUR) 1234.5678 → $1,234.57
getPercentInstance() A percentage, the number is multiplied by 100 0 0.256 → 26%
getCompactNumberInstance(locale, style) Short notation: 1K, 2M (Java 12+) 0 1000 → 1K

A Java NumberFormat example: the same number in three locales. The locale is passed explicitly, so the result does not depend on the computer settings.

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

public class NumberFormatExample1 {
    public static void main(String[] args) {
        double number = 1234567.891;
        Locale[] locales = {Locale.US, Locale.GERMANY, Locale.FRANCE};

        for (Locale locale : locales) {
            NumberFormat numberFormat = NumberFormat.getInstance(locale);
            NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(locale);
            NumberFormat percentFormat = NumberFormat.getPercentInstance(locale);

            System.out.println(locale + ": " + numberFormat.format(number)
                    + " | " + currencyFormat.format(number)
                    + " | " + percentFormat.format(0.256));
        }
    }
}

Output (Java 13+):

en_US: 1,234,567.891 | $1,234,567.89 | 26%
de_DE: 1.234.567,891 | 1.234.567,89 € | 26 %
fr_FR: 1 234 567,891 | 1 234 567,89 € | 26 %

In the fr_FR line the spaces are not ordinary spaces: the group separator is a narrow no-break space (U+202F) and the space before € and % is non-breaking as well. More on this in the tip in the parse section.

Methods without a locale argument, for example NumberFormat.getInstance(), use the default locale, which comes from the operating system settings. On a server it can differ from the locale on the developer's machine, so in code where the result matters pass the locale explicitly.

Currency formatting: getCurrencyInstance

NumberFormat.getCurrencyInstance(locale) formats an amount with the currency symbol of the locale's country and the number of fraction digits that currency needs. The currency is determined by the country, not by the language, so getCurrencyInstance needs a locale with a country: Locale.FRANCE, not Locale.FRENCH.

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

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

        System.out.println(NumberFormat.getCurrencyInstance(Locale.FRANCE).format(price));
        System.out.println(NumberFormat.getCurrencyInstance(Locale.FRENCH).format(price));

        // US format, but the amount is in euros
        NumberFormat usEuro = NumberFormat.getCurrencyInstance(Locale.US);
        usEuro.setCurrency(Currency.getInstance("EUR"));
        System.out.println(usEuro.format(1234.5));
    }
}
// Output:
// 123,46 €
// 123,46 ¤
// €1,234.50

The setCurrency method changes only the currency; separators and symbol position stay the ones from the formatter's locale. This is how a shop shows a price in its own currency to a user with different regional settings.

The ¤ sign instead of a currency

If the output of getCurrencyInstance contains the ¤ symbol, the locale has no country (Locale.FRENCH, Locale.ENGLISH, Locale.GERMAN). Java cannot determine the currency and prints the generic currency sign for the code XXX. The fix is a locale with a country (Locale.GERMANY) or an explicit setCurrency(Currency.getInstance("EUR")).

Number of digits: setMaximumFractionDigits and friends

Four methods control how many digits are printed before and after the decimal separator: setMaximumFractionDigits, setMinimumFractionDigits, setMaximumIntegerDigits and setMinimumIntegerDigits. A minimum adds zeros, a maximum drops extra digits: the fraction is rounded, while the integer part is silently truncated.

Method What it does Non-obvious case
setMaximumFractionDigits(int) Maximum digits after the separator, the last one is rounded If the new maximum is less than the minimum, the minimum is lowered too
setMinimumFractionDigits(int) Minimum digits after the separator, missing ones are filled with zeros If the new minimum is greater than the maximum, the maximum is raised too
setMaximumIntegerDigits(int) Maximum digits in the integer part Leading digits are dropped without an exception: 123.45 → 23.45
setMinimumIntegerDigits(int) Minimum digits in the integer part, missing ones are filled with leading zeros The grouping separator is also put between the zeros: 00,123
setGroupingUsed(boolean) Turns the digit group separator on or off On by default for all formatters except the compact one
getMaximumFractionDigits(), getMinimumFractionDigits() Return the current settings For getInstance() they are 3 and 0

An example with getMaximumFractionDigits(), setMaximumFractionDigits(int digits) and setMaximumIntegerDigits(int digits):

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

public class NumberFormatExample2 {
    public static void main(String[] args) {
        double d = 123.45678;
        NumberFormat nf = NumberFormat.getInstance(Locale.US);

        System.out.println("Max fraction digits "
                + nf.getMaximumFractionDigits() + ": " + nf.format(d));

        nf.setMaximumFractionDigits(7);
        System.out.println("Max fraction digits 7: " + nf.format(d));

        nf.setMaximumIntegerDigits(2);
        System.out.println("Max integer digits 2: " + nf.format(d));
    }
}
// Output:
// Max fraction digits 3: 123.457
// Max fraction digits 7: 123.45678
// Max integer digits 2: 23.45678

The last line shows why setMaximumIntegerDigits must be used with great care. The number 123 turned into 23 and no exception was thrown. For amounts, quantities and identifiers this is almost always a bug.

An example with getMinimumFractionDigits(), setMinimumFractionDigits(int digits) and setMinimumIntegerDigits(int digits):

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

public class NumberFormatExample3 {
    public static void main(String[] args) {
        double d = 123.45678;
        NumberFormat nf = NumberFormat.getInstance(Locale.US);

        System.out.println("Min fraction digits "
                + nf.getMinimumFractionDigits() + ": " + nf.format(d));

        nf.setMinimumFractionDigits(7);
        System.out.println("Min fraction digits 7: " + nf.format(d));

        nf.setMinimumIntegerDigits(5);
        System.out.println("Min integer digits 5: " + nf.format(d));

        nf.setGroupingUsed(false);
        System.out.println("Without grouping: " + nf.format(d));
    }
}
// Output:
// Min fraction digits 0: 123.457
// Min fraction digits 7: 123.4567800
// Min integer digits 5: 00,123.4567800
// Without grouping: 00123.4567800

Leading zeros count as ordinary digits of the integer part, so the group separator is put between them too. If you need numbers like 00123, turn grouping off with setGroupingUsed(false).

Rounding in NumberFormat

By default NumberFormat rounds with RoundingMode.HALF_EVEN (banker's rounding): an exact half is rounded to the nearest even digit. So 2.5 becomes 2 and 3.5 becomes 4. You can change the rule with setRoundingMode.

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

public class RoundingExample {
    public static void main(String[] args) {
        NumberFormat nf = NumberFormat.getIntegerInstance(Locale.US);
        System.out.println(nf.format(2.5) + " " + nf.format(3.5));

        nf.setRoundingMode(RoundingMode.HALF_UP);
        System.out.println(nf.format(2.5) + " " + nf.format(3.5));
    }
}
// Output:
// 2 4
// 3 4

What gets rounded is the exact binary value of the double, not what you typed in the code. For example, the literal 0.135 is stored as 0.13500000000000000888…, so with two fraction digits the result is 0.14 even in HALF_EVEN mode. For money, where an exact half matters, pass a BigDecimal to format.

The parse method: from string to number

The parse(String) method converts a string to a number using the rules of the formatter's locale and returns a Number: a Long if the result is an integer that fits into long, otherwise a Double. If the string does not start with a number, it throws the checked exception java.text.ParseException.

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

public class NumberFormatExample4 {
    public static void main(String[] args) throws ParseException {
        NumberFormat de = NumberFormat.getInstance(Locale.GERMANY);
        System.out.println(de.parse("1234,567"));

        de.setParseIntegerOnly(true);
        System.out.println(de.parse("1234,567"));

        NumberFormat us = NumberFormat.getInstance(Locale.US);
        System.out.println(us.parse("1234,567"));
        System.out.println(us.parse("12abc"));
        System.out.println(us.parse("abc"));
    }
}
// Output:
// 1234.567
// 1234
// 1234567
// 12
// Exception in thread "main" java.text.ParseException: Unparseable number: "abc"

Lines 2–4 of the output explain most parse bugs:

  • setParseIntegerOnly(true) stops parsing at the decimal separator and returns only the integer part.
  • The same string means different numbers in different locales. In the US locale the comma is a grouping separator, so "1234,567" becomes 1234567.
  • parse(String) reads a number from the beginning of the string and silently ignores the tail: "12abc" gives 12. An exception is thrown only if there is no number at the very beginning. Since Java 23 you can enable strict mode with setStrict(true), and then "12abc" also throws ParseException.

The French group separator is not a space

In fr_FR digit groups are separated by a narrow no-break space (U+202F) on Java 13+ and by a no-break space (U+00A0) on older versions, not by an ordinary space. So the string "1 234,5" typed on a keyboard is parsed only up to the space and gives 1. Before parsing user input, replace ordinary spaces: s.replace(' ', '\u202f').

DecimalFormat: your own number pattern

DecimalFormat is a subclass of NumberFormat that formats a number by an explicit pattern such as "#,##0.00". Usually NumberFormat.getInstance() returns exactly a DecimalFormat configured with the locale's pattern. You create a DecimalFormat by hand when the format is fixed in advance (a report, a document number) and must not depend on the user's language.

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

public class DecimalFormatExample {
    public static void main(String[] args) {
        DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(Locale.US);

        System.out.println(new DecimalFormat("#,##0.00", symbols).format(1234.5));
        System.out.println(new DecimalFormat("000.##", symbols).format(5.1));

        NumberFormat compact = NumberFormat.getCompactNumberInstance(
                Locale.US, NumberFormat.Style.SHORT);
        compact.setMaximumFractionDigits(1);
        System.out.println(compact.format(1000) + " " + compact.format(2_500_000));
    }
}
// Output:
// 1,234.50
// 005.1
// 1K 2.5M

In a pattern, 0 means a required digit, # an optional digit, , the place of the group separator and . the decimal separator. Which characters are actually printed in place of , and . is decided by the DecimalFormatSymbols object. Without it, the symbols of the default locale are used.

What does this code print: tricky cases

Here are three fragments where people most often make mistakes, both in interviews and in production code. Try to answer yourself before looking at the output.

1. Percent of 25. The percent formatter multiplies the number by 100, so you must pass a fraction, not a ready percentage:

NumberFormat percent = NumberFormat.getPercentInstance(Locale.US);
System.out.println(percent.format(25));    // Output: 2,500%
System.out.println(percent.format(0.25));  // Output: 25%

2. Comparing with an expected string in a test. The strings look identical but are not equal: the result contains a narrow no-break space (Java 13+).

String s = NumberFormat.getInstance(Locale.FRANCE).format(1234.5);
System.out.println(s);                                          // Output: 1 234,5
System.out.println(s.equals("1 234,5"));                        // Output: false
System.out.println(s.replace('\u202f', ' ').equals("1 234,5")); // Output: true

3. The default format on another server. The call NumberFormat.getInstance().format(0.5) prints 0,5 on a computer with a German locale and 0.5 on a server with an English one. If the string then goes into CSV or JSON, the file breaks only in production. For a machine-readable format pass Locale.ROOT or use DecimalFormat with explicit DecimalFormatSymbols.

Frequently Asked Questions

Does NumberFormat throw NumberFormatException?

No. NumberFormatException is an unchecked exception thrown by Integer.parseInt, Double.parseDouble and new BigDecimal(String), for example on Integer.parseInt("12abc"). The NumberFormat.parse method throws the checked ParseException instead, and only when no number is found at the start of the string. How "12abc" is handled is shown in the section The parse method: from string to number.

NumberFormat or String.format: which one to use?

String.format(locale, "%,.2f", x) is convenient for one-off output and also respects the locale. NumberFormat is needed when you want a currency symbol, percentages, compact notation, rounding settings, or parsing a string back into a number with parse. A configured formatter can be stored in a field and reused.

Can I share one NumberFormat between threads?

No, NumberFormat and DecimalFormat are not thread-safe: they keep mutable state inside, and calling format from several threads at once can mix up the results. Create a formatter in each method, keep it in a ThreadLocal, or synchronize access.

How do I format money stored as BigDecimal without losing precision?

The format method accepts a BigDecimal directly and rounds it using the formatter's RoundingMode without an intermediate double. To parse back, cast the formatter to DecimalFormat and call setParseBigDecimal(true): then parse returns a BigDecimal instead of a Double.

Practice

Reinforce this lesson with hands-on tasks, each with a level and a time estimate.

All module tasks →

Comments

Please log in or register to have a possibility to add comment.