Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Java  Module 2 – Variables, types and operators

Formatted output with printf and formatted

Format numbers, padding and aligned columns with printf, String.format and formatted, and pass a Locale so the output is the same on every computer.

  • Beginner
  • 20 minutes
  • Examples run with Java 25.0.4 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Choose between printf, String.format and formatted, and end lines with %n or \n
  • Format numbers and text with flags, widths and precisions
  • Build aligned tables for console reports from widths and precisions
  • Explain how a Locale changes number formatting, and pass one explicitly
  • Predict how %.2f rounds and what a wrong format string does

Before you start

What you’ll learn · Before you start

What you will learn

  • Choose between printf, String.format and formatted, and end lines with %n or \n
  • Format numbers and text with flags, widths and precisions
  • Build aligned tables for console reports from widths and precisions
  • Explain how a Locale changes number formatting, and pass one explicitly
  • Predict how %.2f rounds and what a wrong format string does

Before you start

On this page

On a US computer String.format("%.2f", 3.5) gives 3.50, and on a German one it gives 3,50, so a line of a CSV file that held two fields suddenly holds three. System.out.printf, String.format and formatted all fill a format string: The text that printf, String.format and formatted fill with values: ordinary characters are copied as they are, and each % starts a format specifier, such as %d or %.2f, that is replaced by the next value. with values, and what they write depends on the values, the format specifier: The part of a format string that starts with % and says how one value is written, as %[flags][width][.precision]conversion: %-20.20s, %,10.2f or %d. Only the % and the conversion are required. such as %d, %.2f, %s and %n, and a locale: A language and region whose writing conventions a program follows, such as en-IN or de-DE: the decimal mark, the grouping separator, the digits and the names of months. In Java it is a java.util.Locale., the writing conventions of a language and region.

You will build a receipt whose columns line up on JDK 25, step through how the formatter applies each part of %,10.2f, and see when to pass a Locale: whenever output must be the same on every computer, as in a CSV file or a log, and the reader’s own locale when a person reads it.

One formatter, three calls: printf prints, the others return text

All three use the same rules, from the class java.util.Formatter. They differ only in what they give back:

ThreeWays.java Java · ThreeWays.java
// One formatter, three ways to call it: printf prints, String.format and formatted return a String.
void main() {
    String name = "Asha";
    int marks = 92;
    double percent = 91.666;
    System.out.printf("%s scored %d (%.1f%%)%n", name, marks, percent);
    String line = String.format("%s scored %d (%.1f%%)", name, marks, percent);
    IO.println(line);
    IO.println("%s scored %d".formatted(name, marks));
}
One format, three calls Python · threeways_session.py
from session import run, version

# ThreeWays.java: printf, String.format and formatted with the same values.
version()
run("java ThreeWays.java")

Output

# openjdk version "25.0.4.1" LTS
$ java ThreeWays.java
Asha scored 92 (91.7%)
Asha scored 92 (91.7%)
Asha scored 92

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 threeways_session.py

Call What it gives back Where the line ends
System.out.printf(format, values…) nothing: it prints at once only where the format says %n
String.format(format, values…) the text, as a String only where the format says %n or \n
"…".formatted(values…) the same text as String.format, from Java 15 only where the format says %n or \n
  • printf adds no line break of its own, so end its format with %n.
  • String.format returns the text to keep, compare or write to a file, and formatted reads well when the format is a literal.
  • %% prints a percent sign, because a single % would start a specifier.
  • IO.println has no formatting of its own: give it a string that is already formatted.

%n writes System.lineSeparator(): \r\n on Windows and \n on macOS and Linux, so printf output looks right in that computer’s terminal and text files. When text must be the same on every computer, or goes somewhere with its own rules, such as a network protocol or the checks of the exercise below, write \n (or whatever that place requires) yourself. These runs used the US English locale, which writes a dot before the decimals; the section on locales shows what changes elsewhere.

Checkpoint

Check: printf, String.format and formatted

  1. Question 1 of 2 Which call gives you the formatted text as a String and prints nothing?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: String.format("%d seats", n)

    String.format and formatted return a String; printf prints. All three use the rules of java.util.Formatter.

  2. Question 2 of 2 What does String.format("%d%%", 50) return?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: "50%"

    A lone % would start a specifier, so a percent sign is written as %%. ThreeWays.java uses it in (%.1f%%).

A format specifier has four parts, and only the conversion is required

A specifier has up to four parts after the %, and only the last, the conversion, is required. The diagram takes %,10.2f apart:

The specifier %,10.2f split into its parts: % starts it, the comma groups digits, 10 is the width, .2 the precision, f a decimal number.%,10.2f1234.5 becomes 1,234.50 after 2 spaces:8 characters, padded on the left to 10%starts it,flags: groupthe digits10width: at least10 characters.2precision: 2decimalsfconversion: adecimal number%[flags][width][.precision]conversionString.format(Locale.US, "%,10.2f", 1234.5)

The parts of a format specifier, and what %,10.2f does to 1234.5

Text description of the diagram

The diagram takes the format specifier %,10.2f apart. The general form is %[flags][width][.precision]conversion; only % and the conversion are required.

  1. % starts the specifier.
  2. The comma is a flag: it puts the locale's grouping separator between groups of digits.
  3. 10 is the width: the result is at least 10 characters long, padded with spaces on the left.
  4. .2 is the precision: for f, the number of digits after the decimal mark.
  5. f is the conversion: the value is written as a decimal number.

With Locale.US, String.format(Locale.US, "%,10.2f", 1234.5) gives two spaces followed by 1,234.50: the number itself is 8 characters, so 2 spaces of padding bring it to the width of 10.

The conversions you will use most, each with a value that Specifiers.java formats below:

Specifier and what it formats Example
%d: whole numbers, such as int and long 42 gives 42
%f: decimal numbers, such as double, float and BigDecimal with .2, 3.14159 gives 3.14
%e: decimal numbers in scientific notation 1234567.891 gives 1.234568e+06
%s: any value, as its toString() text "Pune" gives Pune
%c: a char 'A' gives A
%b: a boolean true gives true
%x: whole numbers in hexadecimal 255 gives ff
%n: a line break, with no value the computer’s line separator
%%: a percent sign, with no value %

Widths, precisions and flags (the characters between % and the width) are easiest to see between brackets:

Specifiers.java Java · Specifiers.java
// Each value printed between [ and ] so that the padding shows.
void main() {
    double pi = 3.14159;
    System.out.printf("[%d] [%5d] [%-5d] [%05d] [%+d]%n", 42, 42, 42, 42, 42);
    System.out.printf("[%f] [%.2f] [%8.2f] [%-8.2f] [%08.2f]%n", pi, pi, pi, pi, pi);
    System.out.printf("[%s] [%8s] [%-8s] [%.3s]%n", "Pune", "Pune", "Pune", "Pune");
    System.out.printf("[%,d] [%e] [%x] [%c] [%b] [%%]%n", 1234567, 1234567.891, 255, 'A', true);
    System.out.printf(Locale.US, "[%f] [%.2f] [%,.2f] [%,10.2f]%n", 1234.5, 1234.5, 1234.5, 1234.5);
}
Widths, precisions and flags Python · specifiers_session.py
from session import run

# Specifiers.java: widths, precisions and flags, each value between [ and ].
run("java Specifiers.java")

Output

$ java Specifiers.java
[42] [   42] [42   ] [00042] [+42]
[3.141590] [3.14] [    3.14] [3.14    ] [00003.14]
[Pune] [    Pune] [Pune    ] [Pun]
[1,234,567] [1.234568e+06] [ff] [A] [true] [%]
[1234.500000] [1234.50] [1,234.50] [  1,234.50]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 specifiers_session.py

The last line of the run builds %,10.2f one part at a time. Step down it from the conversion to the width:

Interactive · stage by stage %,10.2f on 1234.5, one part at a time Step down the specifier from its conversion to its width. Each stage is a real call, and its result is what the run printed.
%,10.2f on 1234.5, one part at a time, step 5 of 5. Showing %f: the conversion, %.2f: add a precision, %,.2f: add a flag, %,10.2f: add a width. In focus: %f: the conversion, %,10.2f: add a width.%f: the conversion1234.500000, six decimals%.2f: add a precision1234.50, two decimals%,.2f: add a flag1,234.50, digits grouped%,10.2f: add a width1,234.50 after 2 spaces

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. The conversion. f says the value is written as a decimal number. With nothing else in the specifier, %f writes six digits after the decimal mark, so 1234.5 becomes 1234.500000.
  2. A precision. .2 sets the number of digits after the decimal mark to two, rounding when digits are dropped. 1234.5 becomes 1234.50.
  3. A flag. The comma flag puts the locale's grouping separator between groups of digits. Under Locale.US that is a comma every three digits, so 1234.50 becomes 1,234.50.
  4. A width. 10 is the smallest number of characters to write. 1,234.50 has 8, so two spaces go in front of it and the number ends at the right edge of a 10-character column.
  5. Only the conversion is required. % and the conversion are the only parts a specifier needs. Flags, width and precision each change one thing, and they always come in the order %[flags][width][.precision]conversion.

  • The width is a minimum. %5d wrote 42 after three spaces: five characters, right-aligned, because numbers and text are right-aligned unless the - flag left-aligns them, as in %-5d.
  • The 0 flag pads a number with zeros (00042), + always shows the sign, and , adds the locale’s grouping separators (1,234,567).
  • For %f the precision is the number of digits after the decimal mark, and %f without one shows six. For %s the precision is a maximum length, so %.3s cut Pune to Pun.
  • %d accepts only whole numbers and %f only decimal numbers. A wrong kind of value is an error, shown later.
  • One more, optional piece may come straight after the %: an argument index such as 2$, as in %2$s, which picks the second value. Every specifier in this lesson leaves it out and takes the next value in order.

Checkpoint

Check: Flags, width, precision and conversion

  1. Question 1 of 2 What does String.format("[%-6s]", "Pune") return?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: "[Pune ]"

    Text and numbers are right-aligned within their width unless the - flag is given, which pads on the right instead, as [%-8s] did in the run.

  2. Question 2 of 2 What does String.format("%.2s", "Pune") return?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: "Pu"

    The run's [%.3s] cut Pune to Pun. A precision of 2 keeps two characters, Pu.

One format string per kind of line keeps the columns straight

A report lines up when every line uses the same widths. Store one format string for each kind of line, and the columns stay put whatever the values are:

Receipt.java Java · Receipt.java
// A fee receipt. Without widths the columns wander; with one format string per kind of line they line up.
void main() {
    System.out.printf("%s %d %.2f%n", "Java course", 1, 4999.00);
    System.out.printf("%s %d %.2f%n", "Mock tests", 3, 448.50);
    IO.println("");

    String head = "%-20s %3s %9s %10s%n";
    String row = "%-20.20s %3d %9.2f %10.2f%n";
    double course = 1 * 4999.00;
    double tests = 3 * 149.50;
    double books = 2 * 350.00;
    double bootcamp = 0 * 2999.00;
    System.out.printf(head, "Item", "Qty", "Price", "Amount");
    System.out.printf(row, "Java course", 1, 4999.00, course);
    System.out.printf(row, "Mock tests", 3, 149.50, tests);
    System.out.printf(row, "Study material", 2, 350.00, books);
    System.out.printf(row, "Data Structures and Algorithms bootcamp", 0, 2999.00, bootcamp);
    System.out.printf("%-34s %10.2f%n", "Total", course + tests + books + bootcamp);
}
Columns without widths, then with them Python · receipt_session.py
from session import run

# Receipt.java: two lines without widths, then the receipt with them.
run("java Receipt.java")

Output

$ java Receipt.java
Java course 1 4999.00
Mock tests 3 448.50

Item                 Qty     Price     Amount
Java course            1   4999.00    4999.00
Mock tests             3    149.50     448.50
Study material         2    350.00     700.00
Data Structures and    0   2999.00       0.00
Total                                 6147.50

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 receipt_session.py

The first two lines have no widths, so each column starts wherever the previous value ended. The format string row gives every column a width of its own, with one space between columns: 45 characters a line.

Column In row Width Aligned
Item %-20.20s 20, and a longer name is cut left
Qty %3d 3 right
Price %9.2f 9, with 2 decimals right
Amount %10.2f 10, with 2 decimals right
  • %-20.20s both pads a short name and cuts a long one, so Data Structures and Algorithms bootcamp became its first 20 characters instead of pushing the other columns to the right.
  • The total line gives Total 34 characters, which is 20 + 1 + 3 + 1 + 9, so the total lands right under the amounts.
  • A quantity of 0 still prints a row, with 0.00 as its amount.

Key idea

A width is only a minimum, so a column stays straight when every line reuses one format string and long text is cut with a precision, as %-20.20s does.

Checkpoint

Check: Columns that line up

  1. One question What does String.format("[%-5.5s]", "Bootcamp") return?

    Choose one answer.

    Show the answer: One question

    Answer: "[Bootc]"

    %-20.20s in the receipt does the same at 20 characters: the width pads a short name and the precision cuts a long one, so the column never moves.

The locale decides the decimal mark and the digit groups

The digits of a number never change, but the decimal mark and the grouping separator belong to the locale:

Locales.java Java · Locales.java
// One amount, five locales: only the Locale argument changes.
void main() {
    double amount = 1234567.891;
    String spec = "%-16s %,15.2f%n";
    System.out.printf(Locale.US, spec, "Locale.US", amount);
    System.out.printf(Locale.GERMANY, spec, "Locale.GERMANY", amount);
    System.out.printf(Locale.FRANCE, spec, "Locale.FRANCE", amount);
    System.out.printf(Locale.of("en", "IN"), spec, "en-IN", amount);
    System.out.printf(Locale.ROOT, spec, "Locale.ROOT", amount);
    System.out.printf(Locale.ROOT, "%-16s %15.2f%n", "no , flag", amount);
    String french = String.format(Locale.FRANCE, "%,d", 1234);
    System.out.printf("Locale.FRANCE groups with U+%04X%n", (int) french.charAt(1));
}
The same amount under five locales Python · locales_session.py
from session import run

# Locales.java: the same amount under five locales.
run("java Locales.java")

Output

$ java Locales.java
Locale.US           1,234,567.89
Locale.GERMANY      1.234.567,89
Locale.FRANCE       1 234 567,89
en-IN               1,234,567.89
Locale.ROOT         1,234,567.89
no , flag             1234567.89
Locale.FRANCE groups with U+202F

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 locales_session.py

Locale %,15.2f of 1234567.891
Locale.US 1,234,567.89
Locale.GERMANY 1.234.567,89
Locale.FRANCE 1 234 567,89, the groups split by U+202F
Locale.of("en", "IN") 1,234,567.89
Locale.ROOT 1,234,567.89
  • Locale.GERMANY swaps the two marks: a comma before the decimals and a dot between groups.
  • Locale.FRANCE separates the groups with U+202F, a narrow no-break space, not an ordinary space. A program that reads such text back and expects a space, or nothing, between the digits fails on it.
  • Locale.ROOT is the neutral locale, with no language and no country: a dot before the decimals and a comma between groups.
  • Without the , flag there are no groups at all, whatever the locale.
  • Make locales with constants such as Locale.US, with Locale.of("en", "IN") (Java 19 and later) or with Locale.forLanguageTag("en-IN"). Constructors such as new Locale("en", "IN") have been deprecated since Java 19.
Number Format Converter (1,234.56 ↔ 1.234,56 ↔ 12,34,567) Paste numbers written in one country's style, such as 1.234.567,89, and get them back in another's.

India-specific: lakh grouping

In India large numbers are usually grouped as 1,23,45,678, with three digits at the right and then groups of two (one lakh is 1,00,000 and one crore is 1,00,00,000). Java 25’s formatter does not do that for en-IN:

IndianGrouping.java Java · IndianGrouping.java
// en-IN and Hindi with Devanagari digits: the digits change, the groups stay at three digits.
void main() {
    int count = 12345678;
    IO.println(String.format(Locale.of("en", "IN"), "%,d", count));
    IO.println(String.format(Locale.forLanguageTag("hi-IN-u-nu-deva"), "%,d", count));
    IO.println(NumberFormat.getInstance(Locale.of("en", "IN")).format(count));
}
en-IN and Devanagari digits on JDK 25 Python · indian_session.py
from session import run

# IndianGrouping.java: en-IN, and Hindi with Devanagari digits, on JDK 25.
run("java IndianGrouping.java")

Output

$ java IndianGrouping.java
12,345,678
१२,३४५,६७८
12,345,678

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 indian_session.py

The formatter puts a separator after every group of N digits, where N is a single grouping size taken from the locale’s DecimalFormat, and when a DecimalFormat pattern has several grouping separators, only the interval after the last one counts. So %,d and NumberFormat both gave 12,345,678. The tag hi-IN-u-nu-deva (Hindi, India, Devanagari numbering) changed the digits to १२,३४५,६७८ but not the groups. The lesson on locales and Indian number formats, in the module on dates, text and locales, builds 1,23,45,678 by hand.

Lakh Crore Converter (to Million, Billion) Convert between lakh and crore and million and billion, with the digits grouped both ways.

Checkpoint

Check: What a locale changes

  1. One question On JDK 25, what does String.format(Locale.of("en", "IN"), "%,d", 12345678) return?

    Choose one answer.

    Show the answer: One question

    Answer: 12,345,678

    The formatter uses one grouping size, taken from the locale's DecimalFormat, and for en-IN that size is 3. So the digits are grouped in threes, as the lesson's run recorded.

Without a Locale argument, the computer’s settings decide

Without a Locale argument, String.format and printf use the default locale: The locale the JVM takes from the computer's settings when it starts, which String.format, printf and formatted use whenever no Locale is passed. The options -Duser.language and -Duser.country set it., which Java takes from the computer’s settings when it starts. Here the same program runs twice, once as a US computer would run it and once as a German one would; the -Duser.language and -Duser.country options set the default locale the way those settings do:

DefaultLocale.java Java · DefaultLocale.java
// A line of a CSV file, written twice: once with the computer's own locale, once with Locale.ROOT.
void main() {
    String name = "Asha";
    double hours = 3.5;
    IO.println(String.format("%s,%.2f", name, hours));                // the default locale decides
    IO.println(String.format(Locale.ROOT, "%s,%.2f", name, hours));   // the same on every computer
}
One CSV line, two default locales Python · default_session.py
from session import run

# The same program twice: -Duser.language and -Duser.country set the default locale, as a computer's settings do.
run("""
java -Duser.language=en -Duser.country=US DefaultLocale.java
java -Duser.language=de -Duser.country=DE DefaultLocale.java
""")

Output

$ java -Duser.language=en -Duser.country=US DefaultLocale.java
Asha,3.50
Asha,3.50
$ java -Duser.language=de -Duser.country=DE DefaultLocale.java
Asha,3,50
Asha,3.50

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 default_session.py

Step through the two runs side by side:

Interactive · step by step One CSV line, two default locales Step through the two runs side by side, the US settings on the left and the German ones on the right.
One CSV line, two default locales, step 6 of 6. Showing -Duser.country=US, -Duser.country=DE, default locale en-US, default locale de-DE, Asha,3.50, Asha,3,50, Asha,3.50, Asha,3.50. In focus: Asha,3,50, Asha,3.50.no Locale givenno Locale given-Duser.country=USlike a US computer-Duser.country=DElike a German computerdefault locale en-USa dot before decimalsdefault locale de-DEa comma before decimalsAsha,3.50two fieldsAsha,3,50three fieldsAsha,3.50with Locale.ROOTAsha,3.50with Locale.ROOT

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. One program, two starts. DefaultLocale.java runs twice. -Duser.language and -Duser.country set the default locale the way a computer's own settings do, to en and US, then to de and DE.
  2. The default locale. The JVM takes its default locale from those settings when it starts. US English writes a dot before the decimals, and German writes a comma.
  3. The US run. The first line calls String.format("%s,%.2f", name, hours) with no Locale, so the default decides. It writes Asha,3.50: a name and an amount, two fields.
  4. The German run. The same call on the German run writes Asha,3,50. A program that splits the line at its commas now finds three fields, and may take 3 as the hours.
  5. Locale.ROOT. The second line passes Locale.ROOT, the neutral locale with no language and no country. Both runs printed Asha,3.50.
  6. The rule. Text that another program reads gets Locale.ROOT. Text that a person reads gets that person's locale, chosen on purpose rather than left to the computer.

On the German run the first line became Asha,3,50: a line of a CSV file that now has three fields instead of two, and the program that reads the file will either fail or quietly take 3 as the hours. The line formatted with Locale.ROOT was the same on both runs. The rule that follows:

  • Text that another program reads, such as CSV and JSON files, logs, file names and values sent over a network, gets Locale.ROOT, so it never depends on where the program runs.
  • Text that a person reads gets that person’s locale, chosen on purpose, not left to whatever the computer happens to use.
  • formatted has no Locale parameter and always uses the default locale. For machine-readable text, call String.format(Locale.ROOT, …) instead.

Key idea

Pass Locale.ROOT for anything a program reads and the reader’s locale for what a person reads: without a Locale, the computer that runs the code chooses the decimal mark.

Checkpoint

Check: The default locale and Locale.ROOT

  1. One question A program runs IO.println(String.format("%.1f", 2.5)); on a computer whose JVM starts with -Duser.language=fr -Duser.country=FR. What does it print?

    Choose one answer.

    Show the answer: One question

    Answer: 2,5

    String.format without a Locale uses the default locale, which the JVM took from those options, so the decimal mark is a comma. String.format(Locale.ROOT, "%.1f", 2.5) would print 2.5 everywhere.

Formatting rounds half up, from the digits a double prints

The lesson on floating point and BigDecimal showed that %.2f turns 2.675 into 2.68. A receipt or an invoice is where that rule decides real amounts:

Rounding.java Java · Rounding.java
// %.2f rounds half up, starting from the digits that Double.toString shows.
void main() {
    System.out.printf(Locale.ROOT, "%.2f %.2f %.2f%n", 2.675, 1.005, 0.125);
    IO.println(new BigDecimal(1.005));                                   // the exact value the double holds
    IO.println(new BigDecimal(1.005).setScale(2, RoundingMode.HALF_UP)); // rounding that exact value
    IO.println(new BigDecimal("1.005").setScale(2, RoundingMode.HALF_UP));
}
%.2f and BigDecimal on the same numbers Python · rounding_session.py
from session import run

# Rounding.java: %.2f against BigDecimal on the same numbers.
run("java Rounding.java")

Output

$ java Rounding.java
2.68 1.01 0.13
1.00499999999999989341858963598497211933135986328125
1.00
1.01

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 rounding_session.py

%.2f turned 2.675 into 2.68, 1.005 into 1.01 and 0.125 into 0.13. The formatter starts from the digits that Double.toString shows, here 1.005, and rounds half up. Yet the double written as 1.005 does not hold 1.005 exactly: the nearest value a double can hold is a little smaller, and new BigDecimal(1.005) prints all of its digits. Step through both roads from the same number:

Interactive · step by step 1.005 rounded two ways, to two different answers Step through what each tool starts from. The left column is %.2f, the right one BigDecimal.
1.005 rounded two ways, to two different answers, step 6 of 6. Showing the double 1.005, Double.toString, %.2f gives 1.01, new BigDecimal(1.005), setScale(2, HALF_UP), new BigDecimal("1.005"). In focus: new BigDecimal("1.005").the double 1.005typed in Rounding.javaDouble.toStringgives 1.005, short digits%.2f gives 1.01half up from 1.005new BigDecimal(1.005)the exact value: 1.00499…setScale(2, HALF_UP)gives 1.00new BigDecimal("1.005")exactly 1.005, so 1.01

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. One number, typed. Rounding.java types 1.005. A double cannot hold 1.005 exactly, so it holds the nearest value it can, which is a little smaller.
  2. What the formatter starts from. %.2f starts from the digits that Double.toString shows for that double: 1.005, the shortest digits that still name it.
  3. Half up. Rounding 1.005 half up to two decimals gives 1.01, which is what the run printed for %.2f.
  4. The exact value. new BigDecimal(1.005) captures the exact value the double holds, 1.00499999999999989341858963598497211933135986328125, the second line of the run.
  5. Rounded again. setScale(2, RoundingMode.HALF_UP) rounds that exact value. The digit after the second decimal is a 4, so the result is 1.00, the third line of the run.
  6. Built from text. new BigDecimal("1.005") holds exactly 1.005, so rounding it half up gives 1.01, the run's last line. Money kept as BigDecimal from text is rounded once, then formatted.

Two tools, two different answers for the same number, so a printed receipt and the books behind it can disagree by a paisa or a cent. Keep money as BigDecimal values made from strings, or as whole numbers of the smallest currency unit in a long, round once with a rounding mode: The rule a BigDecimal follows when it must drop digits, given as a RoundingMode: HALF_UP sends a tie away from zero, HALF_EVEN to the even neighbour, DOWN cuts toward zero, and UNNECESSARY throws if any digit would be lost. More in “Floating point, rounding and BigDecimal” you chose, and only then format the result.

Checkpoint

Check: How %.2f rounds

  1. One question What do String.format(Locale.ROOT, "%.1f", 0.35) and new BigDecimal(0.35).setScale(1, RoundingMode.HALF_UP) give? The double nearest to 0.35 is 0.34999999999999997779…

    Choose one answer.

    A hint

    Ask what each one starts from before it rounds.

    Show the answer: One question

    Answer: 0.4 and 0.3

    This is the 1.005 of the lesson with another number: the formatter starts from Double.toString, here 0.35, and rounds half up to 0.4, while new BigDecimal(double) holds 0.34999…, which rounds to 0.3.

A wrong format string compiles, and fails when it runs

The compiler does not check format strings: below, javac with every warning switched on (-Xlint:all) compiles the example without a word. The mistakes show up only when the lines run:

FormatErrors.java Java · FormatErrors.java
// Three mistakes in format strings, and what each one does.
void main() {
    IO.println(String.format("%s and %s", "tea", "coffee", "milk"));   // one argument too many: ignored
    try {
        String.format("%d marks", 91.5);                               // %d needs a whole number
    } catch (IllegalFormatException e) {
        IO.println(e);
    }
    try {
        String.format("%s scored %d", "Asha");                         // one argument too few
    } catch (IllegalFormatException e) {
        IO.println(e);
    }
}
Three mistakes in format strings Python · errors_session.py
from session import run

# FormatErrors.java compiles without a warning, then shows too many arguments, the wrong kind of value, too few.
run("""
javac -Xlint:all -d classes FormatErrors.java
java FormatErrors.java
""")

Output

$ javac -Xlint:all -d classes FormatErrors.java
$ java FormatErrors.java
tea and coffee
java.util.IllegalFormatConversionException: d != java.lang.Double
java.util.MissingFormatArgumentException: Format specifier '%d'

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 errors_session.py

The mistake What the run showed
One argument too many: "%s and %s" with three values milk simply vanished, without a word
%d with the double 91.5 IllegalFormatConversionException: d != java.lang.Double
One argument too few: "%s scored %d" with one value MissingFormatArgumentException: Format specifier '%d'

The message of each exception names what went wrong: the conversion and the type of value it got, or the specifier that had no value. The try and catch lines catch each exception so that the example can show all three; the exceptions module explains them. In your own code, a format string that is wrong stops the program at that line.

Since only running a line finds these mistakes, make every format string run at least once in a test, the rare ones too, such as the line of an error message or of an empty report. The extra argument is the hardest of the three to catch: nothing fails, so only a test that compares the whole output with the text it expects notices the missing value.

Checkpoint

Check: Mistakes in format strings

  1. One question What does String.format("%s", "a", "b") do with the extra argument?

    Choose one answer.

    Show the answer: One question

    Answer: It ignores it and returns "a"

    An argument too many is ignored, which hides a bug instead of reporting it. An argument too few, or of the wrong kind, throws when the line runs.

Practise: a receipt that lines up

Write receipt(List<Item> items), which returns a receipt such as the one above as a single string: a heading line, one line per item and a total line, each ending with \n. The name gets 20 characters, cut if it is longer, the quantity 3, the price 9 and the amount 10, the last two with 2 decimals, with one space between columns and a dot as the decimal mark on every computer. Item is a record, a small class that holds values: item.name(), item.qty() and item.price() read them, and the module on classes explains records properly. The checks include a long name, a quantity of 0 and an empty list.

FeeReceipt.java Java · exercise/FeeReceipt.java
// Exercise: write receipt(), then run this file: java FeeReceipt.java
// It is done when the last line says "0 of 4 checks failed".

/** One line of the receipt: what was bought, how many, and the price of one. */
record Item(String name, int qty, double price) {}

void main() {
    // Each expected receipt is a text block: a string written over several lines, between """ marks.
    check(List.of(new Item("Java course", 1, 4999), new Item("Mock tests", 3, 149.5)), """
        Item                 Qty     Price     Amount
        Java course            1   4999.00    4999.00
        Mock tests             3    149.50     448.50
        Total                                 5447.50
        """);
    check(List.of(new Item("Data Structures and Algorithms bootcamp", 1, 2999)), """
        Item                 Qty     Price     Amount
        Data Structures and    1   2999.00    2999.00
        Total                                 2999.00
        """);
    check(List.of(new Item("Study material", 0, 350), new Item("Doubt sessions", 12, 99.99)), """
        Item                 Qty     Price     Amount
        Study material         0    350.00       0.00
        Doubt sessions        12     99.99    1199.88
        Total                                 1199.88
        """);
    check(List.of(), """
        Item                 Qty     Price     Amount
        Total                                    0.00
        """);
    IO.println(failed + " of " + total + " checks failed");
    if (failed > 0) System.exit(1);
}

/**
 * The receipt for the items: a heading line, one line per item and a total line, each ending with "\n".
 * Columns: the name left-aligned in 20 characters (cut if longer), the quantity in 3, the price in 9 and the
 * amount in 10, both with 2 decimals, one space between columns, and a dot as the decimal mark everywhere.
 */
String receipt(List<Item> items) {
    return "";   // your code here
}

int total;
int failed;

void check(List<Item> items, String expected) {
    total++;
    String got = receipt(items);
    if (!got.equals(expected)) {
        failed++;
        IO.println("check " + total + " failed. Expected:");
        IO.print(expected);
        IO.println("but receipt() returned:");
        IO.print(got.isEmpty() ? "(an empty string)\n" : got);
    }
}

As it starts, receipt returns "", so all four checks fail:

The starting point Python · exercise_session.py
from session import run

# The checks run against the starter file, where receipt() still returns "": that is the starting point.
run("java exercise/FeeReceipt.java", expect_failure=("java exercise/FeeReceipt.java",))

Output

$ java exercise/FeeReceipt.java
check 1 failed. Expected:
Item                 Qty     Price     Amount
Java course            1   4999.00    4999.00
Mock tests             3    149.50     448.50
Total                                 5447.50
but receipt() returned:
(an empty string)
check 2 failed. Expected:
Item                 Qty     Price     Amount
Data Structures and    1   2999.00    2999.00
Total                                 2999.00
but receipt() returned:
(an empty string)
check 3 failed. Expected:
Item                 Qty     Price     Amount
Study material         0    350.00       0.00
Doubt sessions        12     99.99    1199.88
Total                                 1199.88
but receipt() returned:
(an empty string)
check 4 failed. Expected:
Item                 Qty     Price     Amount
Total                                    0.00
but receipt() returned:
(an empty string)
4 of 4 checks failed
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 exercise_session.py

A first hint: start with the heading, String.format(Locale.ROOT, "%-20s %3s %9s %10s\n", …), then add one formatted line per item in a for (Item item : items) loop while you add up the amounts, and finish with the total line. Pass Locale.ROOT every time, so that a German computer still writes dots.

When the four checks pass, the questions below go through the whole lesson, choosing each next one from the idea your answers show is weakest:

Practice that adapts to you

Practise this lesson

20 questions about 7 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 20 Which of the three calls prints its result straight away?

    Choose one answer.

    Show the answer: Question 1 of 20

    Answer: System.out.printf

    printf prints; String.format and "…".formatted return a String. The run printed the same line three times because ThreeWays.java passed the two returned strings to IO.println.

  2. Question 2 of 20 A program writes a file with printf("%s done%n", job) on a Windows computer, and a test on Linux compares the file with text that ends each line in \n. Why does the test fail, and what fixes it?

    Choose one answer.

    Show the answer: Question 2 of 20

    Answer: %n wrote \r\n, the Windows line separator; write \n in the format when the bytes must be the same everywhere

    %n follows the computer the program runs on, which is right for a terminal or a local text file. Text that must be the same everywhere, such as a file a test compares, or a network protocol, needs the separator written out.

  3. Question 3 of 20 Which conversion formats a long such as 4999L?

    Choose one answer.

    Show the answer: Question 3 of 20

    Answer: %d

    %d is the conversion for whole numbers of any size. Widths and flags such as %,d and %05d work with it as they do with an int.

  4. Question 4 of 20 String.format(Locale.ROOT, "%6.1f", 3.25) returns 3.3 with spaces in front of it. How many spaces?

    Type a number.

    Show the answer: Question 4 of 20

    Answer: 3 spaces

    .1 keeps one decimal, and the formatter rounds 3.25 half up to 3.3, which is 3 characters. The width 6 is a minimum, so 3 spaces are added on the left to make 6 characters. With %-6.1f the 3 spaces would come after it.

  5. Question 5 of 20 What does String.format(Locale.ROOT, "%05.1f", -2.5) return?

    Choose one answer.

    A hint

    The 0 flag pads with zeros, but the sign must stay first.

    Show the answer: Question 5 of 20

    Answer: "-02.5"

    Combine three parts: .1 gives -2.5, four characters; the width 5 needs one more; and the 0 flag pads with zeros placed after the sign, as [00003.14] showed for a positive number.

  6. Question 6 of 20 What happens when a value needs more characters than the width of its specifier?

    Choose one answer.

    Show the answer: Question 6 of 20

    Answer: It is printed in full, and the rest of the line moves to the right

    %5d writes 123456 as all six digits. That is why a receipt cuts long names with a precision instead of trusting the width.

  7. Question 7 of 20 A report prints its rows with "%-12s %4d %8.2f%n". How many characters wide must the label of the total line be, in "%-Ns %8.2f%n", so that its amount lands under the amounts of the rows?

    Type a number.

    A hint

    Add up the widths and the spaces in front of the amount column.

    Show the answer: Question 7 of 20

    Answer: 17 characters

    Before the amount come 12 characters of name, a space, 4 of quantity and another space: 12 + 1 + 4 = 17 before the final space. With %-17s the total's space and amount fall in the same places, as Receipt.java does with 34.

  8. Question 8 of 20 What do String.format("[%-8.8s]", "Mumbai") and String.format("[%-8.8s]", "Thiruvananthapuram") return?

    Choose one answer.

    Show the answer: Question 8 of 20

    Answer: "[Mumbai ]" and "[Thiruvan]"

    Width and precision together make a fixed-width text column: one pads, the other cuts. That is the %-20.20s of the receipt and of the exercise.

  9. Question 9 of 20 Which is the current way to make the locale for English in India?

    Choose one answer.

    Show the answer: Question 9 of 20

    Answer: Locale.of("en", "IN")

    Constants exist for some locales, such as Locale.US. For the others, use Locale.of(language, country) or Locale.forLanguageTag with a tag such as en-IN.

  10. Question 10 of 20 What does String.format(Locale.GERMANY, "%,.2f", 1234.5) return?

    Choose one answer.

    Show the answer: Question 10 of 20

    Answer: 1.234,50

    German writing swaps the two marks: a comma before the decimals and a dot between groups of digits. The , flag asks for the groups, and .2 for two decimals.

  11. Question 11 of 20 A program formats 1234 with String.format(Locale.FRANCE, "%,d", 1234), removes the spaces with replace(" ", "") and parses the result with Integer.parseInt. Why does it throw NumberFormatException?

    Choose one answer.

    Show the answer: Question 11 of 20

    Answer: The French groups are split by U+202F, a narrow no-break space, which replace(" ", "") does not remove

    Text a program reads back should be written with Locale.ROOT, or without the , flag. Text formatted for people may hold separators that are not ordinary spaces, as Locales.java printed: U+202F.

  12. Question 12 of 20 Where does the default locale come from?

    Choose one answer.

    Show the answer: Question 12 of 20

    Answer: From the computer's settings when the JVM starts, or from options such as -Duser.language and -Duser.country

    Without a Locale argument, String.format, printf and formatted use the default locale, which depends on where the program runs.

  13. Question 13 of 20 A program writes amounts into a CSV file that another program imports. Which call keeps the file the same on every computer?

    Choose one answer.

    Show the answer: Question 13 of 20

    Answer: String.format(Locale.ROOT, "%.2f", amount)

    Without a locale, and with Locale.getDefault(), the decimal mark comes from the computer's settings, so a German computer writes 3,50 and adds a field to the CSV line. formatted always uses the default locale. Locale.ROOT is the same everywhere.

  14. Question 14 of 20 A shop shows a reader in Germany the price 1234.5 on a page, and sends the same price to a payment service in a JSON message. Which locales should the two pieces of text use?

    Choose one answer.

    Show the answer: Question 14 of 20

    Answer: The reader's locale, de-DE, for the page, and Locale.ROOT for the JSON message

    The rule of the lesson in a new setting: machine-readable text gets Locale.ROOT, and human-readable text gets the reader's locale, chosen on purpose.

  15. Question 15 of 20 How does %.2f round a value whose dropped digits are exactly a half, such as 2.675?

    Choose one answer.

    Show the answer: Question 15 of 20

    Answer: Half up, starting from the digits Double.toString shows

    The Formatter rounds half up, from the digits of Double.toString. That is why %.2f and a BigDecimal made from the same double can disagree.

  16. Question 16 of 20 What does String.format(Locale.ROOT, "%.2f", 1.005) return?

    Choose one answer.

    Show the answer: Question 16 of 20

    Answer: 1.01

    The formatter rounds half up from the digits Double.toString shows, which are 1.005, so it returns 1.01. Rounding the exact value the double holds, 1.00499999…, gives 1.00 instead, which is what new BigDecimal(1.005).setScale(2, RoundingMode.HALF_UP) returns.

  17. Question 17 of 20 An invoice prints amounts with %.2f, and the books keep them as new BigDecimal(amount) rounded half up. Why can the two disagree by a paisa?

    Choose one answer.

    Show the answer: Question 17 of 20

    Answer: They round different numbers, the shortest digits of the double and its exact binary value

    Both tools rounded half up, but from different starting points, because the amount went through a double. Built from text, new BigDecimal("1.005") gave 1.01, the same as the invoice.

  18. Question 18 of 20 Does javac -Xlint:all warn about String.format("%d marks", 91.5)?

    Choose one answer.

    Show the answer: Question 18 of 20

    Answer: No, the compiler does not look inside format strings

    Mistakes in format strings show up only at run time, which is why a line that formats should run in a test at least once.

  19. Question 19 of 20 What happens when String.format("%d", 2.5) runs?

    Choose one answer.

    Show the answer: Question 19 of 20

    Answer: It throws an IllegalFormatConversionException

    %d is for whole numbers only, and 2.5 is a double. The compiler does not look inside format strings, so the line compiles, and the formatter throws IllegalFormatConversionException: d != java.lang.Double when it runs.

  20. Question 20 of 20 What happens when String.format("%s scored %d (%d%%)", "Asha", 92) runs?

    Choose one answer.

    Show the answer: Question 20 of 20

    Answer: It throws MissingFormatArgumentException for the second %d

    Count the specifiers that take a value: %s, %d and %d. With two arguments, the last %d has none, and the formatter throws MissingFormatArgumentException: Format specifier '%d'.

Interview questions

Fresher · In printf("%8.2f", x), what do 8 and .2 mean, and what happens when the number needs more than 8 characters?

8 is the width: the minimum number of characters written. .2 is the precision: for f, the number of digits after the decimal mark, rounded half up from the digits that Double.toString shows. A shorter result is padded with spaces on the left, so it is right-aligned; the - flag pads on the right instead, and the 0 flag pads with zeros. The width is only a minimum: a number that needs 12 characters prints all 12 and pushes the rest of the line to the right, so the columns stop lining up, but no digit is ever cut off. For %s, by contrast, a precision is a maximum: %-10.10s pads a name to 10 characters and also cuts a longer one to 10.

Two longer questions of this module start from this lesson: a nightly CSV job that breaks after a move to a new server, and an invoice that rounds one line differently from the books.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid

    A nightly job writes amounts into a CSV file with String.format("%.2f", amount). It ran correctly for months. After a move to a new server, the importer rejects the file because every line now has an extra column. What happened, how do you fix it, and how do you make sure it cannot happen again?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    An invoice prints each line with String.format("%.2f", price * qty). For one line of 1 x 1.005 it prints 1.01, while the accounting system, which uses BigDecimal, books 1.00. Explain both results, and decide how the invoice and the books should round.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • printf prints, String.format returns a string, and formatted is String.format called on its string; all three follow the rules of java.util.Formatter, and %n ends a line with the computer’s separator.
  • A specifier reads %[flags][width][.precision]conversion: %-20.20s is a left-aligned, cut-to-fit column and %,10.2f a grouped number with 2 decimals in 10 characters.
  • The width is a minimum, so reuse one format string per kind of line to keep columns aligned, and cut long text with a precision.
  • The locale decides the decimal mark and the grouping separator, and on JDK 25 %,d for en-IN still groups digits in threes: 12,345,678.
  • Without a Locale, the default locale decides: pass Locale.ROOT for anything a program reads, and the reader’s locale for what people read; formatted always uses the default locale.
  • %.2f rounds half up from the digits of Double.toString, so 1.005 prints as 1.01, and a wrong format string compiles and then fails, or silently drops arguments, when it runs.

Terms in this lesson

default locale
The locale the JVM takes from the computer's settings when it starts, which String.format, printf and formatted use whenever no Locale is passed. The options -Duser.language and -Duser.country set it.
format specifier
The part of a format string that starts with % and says how one value is written, as %[flags][width][.precision]conversion: %-20.20s, %,10.2f or %d. Only the % and the conversion are required.
format string
The text that printf, String.format and formatted fill with values: ordinary characters are copied as they are, and each % starts a format specifier, such as %d or %.2f, that is replaced by the next value.
locale
A language and region whose writing conventions a program follows, such as en-IN or de-DE: the decimal mark, the grouping separator, the digits and the names of months. In Java it is a java.util.Locale.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.