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.
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:
// 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));
} 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 |
printfadds no line break of its own, so end its format with%n.String.formatreturns the text to keep, compare or write to a file, andformattedreads well when the format is a literal.%%prints a percent sign, because a single%would start a specifier.IO.printlnhas 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
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 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.
- % starts the specifier.
- The comma is a flag: it puts the locale's grouping separator between groups of digits.
- 10 is the width: the result is at least 10 characters long, padded with spaces on the left.
- .2 is the precision: for f, the number of digits after the decimal mark.
- 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:
// 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);
} 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:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- 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.
- A precision. .2 sets the number of digits after the decimal mark to two, rounding when digits are dropped. 1234.5 becomes 1234.50.
- 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.
- 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.
- 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.
%5dwrote 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
0flag pads a number with zeros (00042),+always shows the sign, and,adds the locale’s grouping separators (1,234,567). - For
%fthe precision is the number of digits after the decimal mark, and%fwithout one shows six. For%sthe precision is a maximum length, so%.3scutPunetoPun. %daccepts only whole numbers and%fonly 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 as2$, 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
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:
// 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);
} 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.20sboth pads a short name and cuts a long one, soData Structures and Algorithms bootcampbecame its first 20 characters instead of pushing the other columns to the right.- The total line gives
Total34 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.00as 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
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:
// 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));
} 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.GERMANYswaps the two marks: a comma before the decimals and a dot between groups.Locale.FRANCEseparates 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.ROOTis 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, withLocale.of("en", "IN")(Java 19 and later) or withLocale.forLanguageTag("en-IN"). Constructors such asnew Locale("en", "IN")have been deprecated since Java 19.
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:
// 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));
} 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.
Checkpoint
Check: What a locale changes
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:
// 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
} 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:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- 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.
- 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.
- 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.
- 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.
- Locale.ROOT. The second line passes Locale.ROOT, the neutral locale with no language and no country. Both runs printed Asha,3.50.
- 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.
formattedhas noLocaleparameter and always uses the default locale. For machine-readable text, callString.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
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:
// %.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));
} 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:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- 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.
- 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.
- Half up. Rounding 1.005 half up to two decimals gives 1.01, which is what the run printed for %.2f.
- The exact value. new BigDecimal(1.005) captures the exact value the double holds, 1.00499999999999989341858963598497211933135986328125, the second line of the run.
- 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.
- 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
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:
// 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);
}
} 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
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.
// 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:
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.
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.
-
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?
-
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.
Key takeaways
printfprints,String.formatreturns a string, andformattedisString.formatcalled on its string; all three follow the rules ofjava.util.Formatter, and%nends a line with the computer’s separator.- A specifier reads
%[flags][width][.precision]conversion:%-20.20sis a left-aligned, cut-to-fit column and%,10.2fa 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
%,dforen-INstill groups digits in threes:12,345,678. - Without a
Locale, the default locale decides: passLocale.ROOTfor anything a program reads, and the reader’s locale for what people read;formattedalways uses the default locale. %.2frounds half up from the digits ofDouble.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
Kept only in this browser. Your Learn progress