Contents
- Common Format Specifiers
- Formatting Numbers
- Width, Precision, and Alignment
- formatted() — Java 15+
- Locale-Aware Formatting
String.format() and printf() use percent-prefixed format specifiers. The most commonly needed ones are: %s for any object (calls toString()), %d for decimal integers, %f for floating-point numbers, %n for a platform-independent newline (prefer over \n in format strings), %b for booleans, %c for characters, and %x for hexadecimal. Use %% to emit a literal percent sign. %e formats numbers in scientific notation, and %o uses octal.
// String.format(format, args...) — returns a String
// System.out.printf(format, args...) — prints, no newline
// System.out.println(String.format(...)) — print with newline
// Common specifiers:
// %s — String (or toString() of any Object)
// %d — decimal integer (int, long)
// %f — floating point (double)
// %e — scientific notation
// %b — boolean
// %c — character
// %n — platform newline (prefer over \n for portability)
// %x — hexadecimal integer
// %o — octal integer
// %% — literal percent sign
String name = "Alice";
int age = 30;
double score = 95.678;
String msg = String.format("Name: %s, Age: %d, Score: %.2f", name, age, score);
System.out.println(msg); // Name: Alice, Age: 30, Score: 95.68
System.out.printf("Hello, %s! You scored %d points.%n", name, (int)score);
// %b — boolean
boolean active = true;
System.out.printf("Active: %b%n", active); // Active: true
System.out.printf("Active: %b%n", null); // Active: false (null → false)
// %x — hexadecimal
System.out.printf("0x%X%n", 255); // 0xFF
System.out.printf("0x%08x%n", 255); // 0x000000ff (zero-padded to 8)
%.2f rounds a floating-point number to 2 decimal places. %,d adds thousands separators (locale-dependent by default). %08d zero-pads an integer to a minimum width of 8 digits. %e formats in scientific notation; combine with precision like %.2e for fewer significant digits. For fully locale-aware number formatting — where the decimal separator and grouping symbol must match the user's locale — use NumberFormat or pass an explicit Locale to String.format().
double pi = Math.PI; // 3.141592653589793
// .Nf — N decimal places
System.out.printf("%.0f%n", pi); // 3
System.out.printf("%.2f%n", pi); // 3.14
System.out.printf("%.5f%n", pi); // 3.14159
// %e — scientific notation
System.out.printf("%e%n", 1234567.89); // 1.234568e+06
System.out.printf("%.2e%n", 1234567.89); // 1.23e+06
// %,d — thousands separator (locale-dependent)
System.out.printf("%,d%n", 1_000_000); // 1,000,000
System.out.printf("%,.2f%n", 1_234_567.89); // 1,234,567.89
// %+d — force sign
System.out.printf("%+d, %+d%n", 42, -42); // +42, -42
// Formatting with java.text.NumberFormat (locale-aware)
NumberFormat nf = NumberFormat.getInstance(Locale.GERMANY);
System.out.println(nf.format(1234567.89)); // 1.234.567,89 (German locale)
NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.US);
System.out.println(currency.format(1234.56)); // $1,234.56
Width is the minimum field width — the formatted value is padded to at least that many characters. Numbers are right-aligned by default; the - flag switches to left-alignment (%-10s). The + flag forces a sign prefix on positive numbers (%+d). For strings, precision truncates: %.5s takes only the first 5 characters. Combining width and precision — %8.2f — means at least 8 characters wide with exactly 2 decimal places, right-aligned. Zero-padding replaces the space padding with zeros: %08d.
// Width — minimum field width (right-aligned by default)
System.out.printf("[%10s]%n", "hi"); // [ hi]
System.out.printf("[%10d]%n", 42); // [ 42]
// Left-align with -
System.out.printf("[%-10s]%n", "hi"); // [hi ]
System.out.printf("[%-10d]%n", 42); // [42 ]
// Zero-pad numbers
System.out.printf("[%010d]%n", 42); // [0000000042]
System.out.printf("[%010.2f]%n", 3.14); // [0000003.14]
// Width and precision together
System.out.printf("[%10.2f]%n", 3.14159); // [ 3.14] (width 10, 2 decimals)
// Building a table
String fmt = "%-20s %5d %8.2f%n";
System.out.printf(fmt, "Widget", 150, 4.99);
System.out.printf(fmt, "Gadget", 1200, 12.50);
System.out.printf(fmt, "Sprocket", 35, 0.89);
// Widget 150 4.99
// Gadget 1200 12.50
// Sprocket 35 0.89
// Argument index — reuse an argument
System.out.printf("%1$s loves %1$s.%n", "Java"); // Java loves Java.
String.formatted(args) is an instance method added in Java 15 that is functionally identical to String.format(this, args). It uses the same format specifiers and produces the same results — the difference is purely stylistic. It is most readable when the format string is already defined as a constant or text block and you want to fill in arguments inline: "Hello, %s!".formatted(name) reads more naturally in a chain than the equivalent String.format() call. There is no locale parameter on the instance method; use String.format(Locale, format, args) when an explicit locale is needed.
// formatted() is an instance method on String — same as String.format() but cleaner in chains
String msg = "Name: %s, score: %.1f".formatted("Bob", 88.5);
System.out.println(msg); // Name: Bob, score: 88.5
// Useful with text blocks (Java 15+) for multi-line templates
String json = """
{
"name": "%s",
"age": %d,
"score": %.2f
}
""".formatted("Alice", 30, 95.67);
System.out.println(json);
// Chaining in stream pipelines
List.of("Alice", "Bob", "Charlie")
.stream()
.map(name -> "Hello, %s!".formatted(name))
.forEach(System.out::println);
// formatted() vs String.format() — functionally identical
String a = String.format("Value: %d", 42); // traditional
String b = "Value: %d".formatted(42); // Java 15+, more readable
When no Locale is given, String.format() uses Locale.getDefault(), which varies by machine. On a system with a German locale, %.2f will produce 1234,50 (comma as decimal separator), which will fail to parse in code that expects 1234.50. Always pass Locale.US or Locale.ROOT when generating numbers or dates that must be parsed programmatically or transmitted over a network. Reserve locale-sensitive formatting for text displayed to the end user.
// String.format with Locale — avoid locale-sensitive defaults
String us = String.format(Locale.US, "%.2f", 1234.5); // "1234.50"
String de = String.format(Locale.GERMANY, "%.2f", 1234.5); // "1234,50"
String fr = String.format(Locale.FRANCE, "%.2f", 1234.5); // "1 234,50"
// Formatted output using Locale
System.out.printf(Locale.US, "%,.2f%n", 1_234_567.89); // 1,234,567.89
// Dates with DateTimeFormatter (modern approach)
LocalDate date = LocalDate.of(2025, 3, 15);
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("MMMM d, yyyy", Locale.US);
System.out.println(date.format(fmt)); // March 15, 2025
DateTimeFormatter deFmt = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.GERMANY);
System.out.println(date.format(deFmt)); // 15. März 2025
// MessageFormat — handles plurals and locale-sensitive messages
String pattern = "There {0,choice,0#are no files|1#is one file|1<are {0} files}.";
System.out.println(MessageFormat.format(pattern, 0)); // There are no files.
System.out.println(MessageFormat.format(pattern, 1)); // There is one file.
System.out.println(MessageFormat.format(pattern, 5)); // There are 5 files.
Always pass an explicit Locale to String.format() when formatting numbers or dates that will be parsed or compared programmatically. Relying on the default locale can cause subtle bugs when your application runs in a different locale (e.g., decimal point vs. decimal comma).