Formatting with formatted() and String.format
Format numbers, padding and columns with format specifiers, and keep machine-readable output stable by passing a locale.
String.format(Locale.ROOT, "%-8s %5d %9.2f", sku, qty, total)
"Order %s for %s".formatted(id, name)String.format(pattern, args...) and the instance method pattern.formatted(args...), added in Java 15, both fill a template with values. They produce the same output; formatted reads better after a text block. System.out.printf uses the same rules and prints directly.
Specifiers you will use
| Specifier | Meaning | Example result |
|---|---|---|
%s | Any value via toString, null as null | Ada |
%d | Integer types | 42 |
%.2f | Floating point or BigDecimal, 2 decimals, half-up rounding | 19.99 |
%,d | Grouping separators | 1,234,567 |
%8s, %-8s | Pad to width 8, right or left aligned | Ada |
%06d | Zero-pad to width 6 | 000042 |
%x | Hexadecimal | ff |
%%, %n | A literal %, the platform line separator |
Locale changes the output
Both methods use the JVM's default locale unless told otherwise. On a server with a German default, String.format("%.2f", 1.5) gives 1,50, which breaks a CSV file, a JSON number or a value another service parses. For anything a program will read, call String.format(Locale.ROOT, ...). formatted has no locale parameter, so keep it for text people read. Even %d is not safe in machine output: a locale with its own digits, such as th-TH-u-nu-thai, prints 42 as Thai numerals.
Mistakes that only fail at run time
The compiler does not check patterns against arguments. %d with a double throws IllegalFormatConversionException, and too few arguments throws MissingFormatArgumentException. Too many arguments are silently ignored. Cover formatting code with a test.
%n is \r\n on Windows. In HTTP bodies, JSON and files with a defined format, write \n explicitly.
Money and performance
Format money from a BigDecimal, not a double; %.2f accepts both, but only the first holds the exact amount. Formatting parses the pattern on every call, so it is slower than concatenation. That does not matter for a response, but in a hot loop or a log statement that is usually disabled, avoid building the String at all; loggers take their own placeholders for that reason.
Example
When to use it
- Render a plain-text order summary with aligned SKU, quantity and total columns for a confirmation email.
- Write amounts to a CSV export with String.format(Locale.ROOT, "%.2f", total) so every server produces the same file.
- Generate zero-padded, human-readable reference numbers such as INV-000042 for invoices.
More examples
Truncating, grouping and signs
A precision on %s cuts the text to that many characters, so %-10.10s gives a column that is exactly ten wide whatever the input. With Locale.ROOT the grouping separator is always a comma, whichever locale the server runs with.
void main() {
IO.println(String.format(Locale.ROOT, "[%.3s]", "Alexander"));
IO.println(String.format(Locale.ROOT, "[%-10.10s]", "A very long product name"));
IO.println(String.format(Locale.ROOT, "%,d", 1234567));
IO.println(String.format(Locale.ROOT, "%+d / %+d", 5, -5));
}
// Output:
// [Ale]
// [A very lon]
// 1,234,567
// +5 / -5Padding, zero-fill, hex and literal percent
Zero-padding gives fixed-width reference numbers, and %08x is a common way to show a hash or checksum. A single % must be written as %%, or the formatter treats it as the start of a specifier.
void main() {
IO.println("ORD-%06d".formatted(42));
IO.println("%x / %X / %08x".formatted(255, 255, 48879));
IO.println("[%5s][%-5s]".formatted("ab", "ab"));
IO.println("%d%% done".formatted(75));
IO.println("%s and %s".formatted(null, true));
}
// Output:
// ORD-000042
// ff / FF / 0000beef
// [ ab][ab ]
// 75% done
// null and truePattern mistakes fail at run time
The first two mistakes compile and only throw when the line runs, perhaps in a rarely used error path. The third is silent: the extra argument is ignored, so a missing placeholder can go unnoticed.
void main() {
try {
IO.println(String.format("%d items", 2.5));
} catch (IllegalFormatConversionException e) {
IO.println("Bad conversion: " + e.getMessage());
}
try {
IO.println(String.format("%s owes %s", "Ada"));
} catch (MissingFormatArgumentException e) {
IO.println("Missing: " + e.getMessage());
}
IO.println(String.format("%s", "Ada", "extra"));
}
// Output:
// Bad conversion: d != java.lang.Double
// Missing: Format specifier '%s'
// Ada
Discussion