String Module ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-10-16

String Formatting in Java: String.format(), printf() and Formatter

On a laptop with German regional settings, String.format("%.2f", 0.5) returns "0,50" instead of "0.50" — and the very next Double.parseDouble(...) fails with NumberFormatException: For input string: "0,50". Surprises like this disappear once you understand how string formatting in Java actually works.

String formatting in Java means building a string from a template in which format specifiers (%d, %s, %.2f and others) are replaced with argument values. Formatted output is produced by String.format(), String.formatted() (Java 15+), System.out.printf() and the java.util.Formatter class, which powers all of them. You can format numbers, strings, characters, dates and times.

String.format(), printf() and formatted(): which one to use

If you need the result as a string, use String.format() or "template".formatted(); if you want to print it right away, use System.out.printf(); if the text is assembled in several steps or written to a file, use the Formatter class. All of them understand exactly the same template syntax.

Method What it returns Since When to use it
String.format(template, args...) A new string Java 5 You need the formatted string itself (Java's sprintf)
"template".formatted(args...) A new string Java 15 Same as above, but shorter; great with text blocks
System.out.printf(...), System.out.format(...) The same PrintStream; prints to the console Java 5 Formatted console output
PrintWriter.printf(...) The same PrintWriter; writes to a stream Java 5 Writing to a file or a network stream
new Formatter(...).format(...) The same Formatter; accumulates the result Java 5 Building text from several calls, writing to an Appendable or a file

System.out is a static field of type PrintStream, which is why it has the printf() and format() methods. They are two names for the same method: printf() exists to make C programmers feel at home. Unlike print() and println(), which print a single value as is, printf() takes a template plus any number of arguments. Under the hood, every method in the table uses Formatter.

Throughout this lesson, the output in comments is shown for Locale.US. With a locale such as German or French, you will see a comma instead of the decimal point (details in the Locale section).

public class PrintfExample {
    public static void main(String[] args) {
        System.out.printf("Formatted string: %.2f%n", 1000.0 / 3.0);
        System.out.format("%s, next year you will be %d%n", "John", 23);

        String str = String.format("Price: $%.2f", 99.5);
        System.out.println(str);

        String greeting = "Hello, %s! You have %d new messages.".formatted("Anna", 5);
        System.out.println(greeting);
    }
}
// Output:
// Formatted string: 333.33
// John, next year you will be 23
// Price: $99.50
// Hello, Anna! You have 5 new messages.

The formatted() method shines with text blocks (Java 15+): you write the template as is, without concatenation or \n.

String json = """
        {"name": "%s", "age": %d}
        """.formatted("Anna", 30);
System.out.print(json);
// Output: {"name": "Anna", "age": 30}

Format specifier syntax

A format specifier is a part of the template that starts with % and describes how to print the next argument. Its general form is %[argument_index$][flags][width][.precision]conversion; only the % sign and the conversion character are required, everything else is optional.

%[argument_index$][flags][width][.precision]conversion
Part What it sets Example
argument_index$ Argument number (starting at 1), followed by $ %2$s — the second argument
flags Alignment, sign, leading zeros, grouping separators %-10s, %,d, %+d
width Minimum field width in characters %8d
.precision Digits after the decimal point for numbers; maximum length for strings %.2f, %.3s
conversion Conversion type (required) d, s, f, x

The argument index lets you reuse one value several times or change the order of arguments. The %<s form means "the same argument as the previous specifier":

public class ArgumentIndexExample {
    public static void main(String[] args) {
        System.out.printf("Hello %1$s!%n%1$s, how are you?%n"
                        + "Welcome to the site %2$s%n",
                "John", "www.site.com");
        System.out.printf("%d in hexadecimal: %<x%n", 255);
    }
}
// Output:
// Hello John!
// John, how are you?
// Welcome to the site www.site.com
// 255 in hexadecimal: ff

Java format specifiers: table

The conversion character defines how the argument is interpreted: %d is an integer, %f a floating-point number, %s a string, %c a character, %b a boolean. The argument type must match the specifier, otherwise you get an IllegalFormatConversionException at runtime.

Specifier What it prints Example Result
%d Decimal integer (int, long, BigInteger…) format("%d", 42) 42
%s / %S String representation (toString()) format("%S", "java") JAVA
%f Floating-point number in decimal notation, 6 digits after the point by default format("%f", 3.14159) 3.141590
%e / %E Floating-point number in scientific notation format("%e", 12345.678) 1.234568e+04
%g / %G Floating-point number: scientific notation if, after rounding, the value is less than 10-4 or at least 10precision, otherwise decimal. Precision = total number of significant digits (6 by default) format("%g", 12345.678)
format("%g", 0.00001234)
12345.7
1.23400e-05
%a / %A Floating-point number in hexadecimal notation format("%a", 1.0) 0x1.0p0
%x / %X Integer in hexadecimal format("%X", 255) FF
%o Integer in octal format("%o", 8) 10
%c / %C Character (char or an int code point) format("%c", 65) A
%b / %B Boolean: false for null, the value itself for a Boolean, true for any other object format("%b", null) false
%h / %H The argument's hash code in hexadecimal format("%h", "hi") d01
%t / %T Date and time (with a suffix: %tY, %tF…) format("%tY", LocalDate.of(2026, 10, 16)) 2026
%n The platform-specific line separator format("a%nb") a and b on separate lines
%% A literal % sign format("%d%%", 50) 50%

Uppercase variants (%S, %X, %E, %G, %A, %B, %H, %C, %T) work like their lowercase counterparts but convert the result to upper case: %x gives ff, %X gives FF; %b gives true, %B gives TRUE. There are no uppercase forms of %d, %f and %o.

What does this code print?

System.out.printf("%b %b %b%n", "false", null, 0);

Answer: true false true. The string "false" is not a Boolean but an ordinary non-null object, so %b turns it into true. The number 0 also gives true: in Java, unlike C, zero does not mean "false". Only null becomes false.

Width and precision

Width (%10s) sets the minimum number of characters in a field: a shorter value is padded with spaces on the left, or on the right with the - flag. Precision (%.2f) sets the number of digits after the decimal point for floating-point numbers, rounding with HALF_UP; for strings (%.3s) it sets the maximum length, and the rest is cut off.

public class WidthPrecisionExample {
    public static void main(String[] args) {
        double x = 1000.0 / 3.0;
        System.out.println("Unformatted: " + x);
        System.out.printf("[%.2f]%n", x);
        System.out.printf("[%8.2f]%n", x);
        System.out.printf("[%-8.2f]%n", x);
        System.out.printf("[%10s][%-10s]%n", "Java", "Java");
        System.out.printf("[%.3s]%n", "Formatter");
        System.out.printf("[%3s]%n", "Formatter");
    }
}
// Output:
// Unformatted: 333.3333333333333
// [333.33]
// [  333.33]
// [333.33  ]
// [      Java][Java      ]
// [For]
// [Formatter]

The last line shows a non-obvious detail: width is a minimum, not a maximum. A longer value is printed in full, and your columns drift apart. To truncate a string you need precision: %-10.10s always prints exactly 10 characters.

Width and alignment are what you need to print a table in the console:

public class TableExample {
    public static void main(String[] args) {
        System.out.printf("%-10s%8s%n", "Item", "Price");
        System.out.printf("%-10s%8.2f%n", "Bread", 45.5);
        System.out.printf("%-10s%8.2f%n", "Coffee", 89.9);
    }
}
// Output:
// Item         Price
// Bread        45.50
// Coffee       89.90

Formatting float and double with %f

The %f, %e and %g specifiers accept float, double, their wrappers and BigDecimal; there is no separate specifier for float and no %lf for double. Integer specifiers never convert floating-point values: %d with a float argument throws IllegalFormatConversionException: d != java.lang.Float.

float price = 19.99f;
double discount = 12.5;
System.out.println(String.format("%.2f", price));
System.out.println(String.format("%.1f%%", discount));
System.out.println(String.format("%08.3f", -3.14159));
// Output:
// 19.99
// 12.5%
// -003.142

Format flags

Flags go right after % (or after the argument index) and control alignment, the sign of a number, leading zeros and grouping separators. Several flags can be combined: %,(.2f prints a negative number in parentheses and with grouping separators.

Flag What it does Example Result
- Left-justify (requires a width) format("[%-5d]", 42) [42 ]
0 Pad with zeros instead of spaces (numbers only, requires a width) format("%05d", 42) 00042
+ Always print the sign format("%+d", 42) +42
space Put a space before positive numbers (to line them up with negative ones) format("[% d]", 42) [ 42]
, Locale-specific grouping separators format("%,d", 1234567) 1,234,567
( Enclose negative numbers in parentheses format("%(d", -42) (42)
# Alternate form: 0x prefix for %x, 0 for %o format("%#x", 255) 0xff
public class FlagsExample {
    public static void main(String[] args) {
        System.out.printf("%,.2f%n", 10000.0 / 3.0);
        System.out.printf("%,(.2f%n", -10000.0 / 3.0);
        System.out.printf("%09.2f%n", 10000.0 / 3.0);
    }
}
// Output:
// 3,333.33
// (3,333.33)
// 003333.33

Incompatible flags throw an exception: + together with a space, or - together with 0, gives IllegalFormatFlagsException, and - or 0 without a width gives MissingFormatWidthException.

The Formatter class

The java.util.Formatter class is the engine behind formatted output in Java: String.format() and printf() create one internally. You use Formatter directly when the text is built from several format() calls or when the result should go straight into a StringBuilder, a file or a stream.

The format() method of Formatter converts its arguments according to the template and appends the result to the accumulated text, which you get back with toString(). A minimal Java Formatter example:

import java.util.Formatter;

public class FormatterExample {
    public static void main(String[] args) {
        Formatter formatter = new Formatter();
        formatter.format("This %s is about %n%S %c", "book", "java", '8');
        System.out.println(formatter);
    }
}
// Output:
// This book is about
// JAVA 8

The Formatter(Appendable, Locale) constructor writes directly into a StringBuilder and sets the locale at the same time. Formatter implements Closeable, so when it writes to a file, close it with try-with-resources. All constructors and conversions are listed in the official Formatter Javadoc.

import java.util.Formatter;
import java.util.Locale;

public class FormatterBuilderExample {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        Formatter formatter = new Formatter(sb, Locale.US);
        formatter.format("%-8s%6.2f%n", "Tea", 3.5);
        formatter.format("%-8s%6.2f%n", "Coffee", 12.0);
        System.out.print(sb);
    }
}
// Output:
// Tea       3.50
// Coffee   12.00

Locale: dot or comma

Number and date formatting depends on the locale: without an explicit Locale, String.format() and printf() use the system default (Locale.getDefault(Locale.Category.FORMAT)). In German and French the decimal separator is a comma, and the grouping separator is a dot or a space, so the same program prints different strings on different machines.

import java.util.Locale;

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

        System.out.println(String.format(Locale.US, "%,.2f", price));
        System.out.println(String.format(Locale.GERMANY, "%,.2f", price));
        System.out.println(String.format(Locale.FRANCE, "%,.2f", price));
        System.out.println(String.format(Locale.ROOT, "%.2f", price));
        System.out.println(String.format(Locale.GERMANY, "%.2f", 0.5));
    }
}
// Output:
// 1,234.57
// 1.234,57
// 1 234,57
// 1234.57
// 0,50

The French line looks like it contains an ordinary space, but it does not: since Java 13 (CLDR locale data) the French grouping separator is the narrow no-break space U+202F, and older JDKs used the no-break space U+00A0. A test such as assertEquals("1 234,57", result) typed with a regular space fails, and so does parsing that string back with code that expects plain spaces.

format() and printf() have an overload that takes a Locale as the first parameter. formatted() has no such overload — it always uses the default locale. The new Locale("de", "DE") constructor is deprecated since Java 19; use the constants (Locale.GERMANY), Locale.forLanguageTag("de-DE") or Locale.of("de", "DE") (Java 19+) instead.

Rule of thumb

For data that another program will read (CSV, JSON, SQL, logs that get parsed), pass Locale.ROOT or Locale.US explicitly. Rely on the default locale only for text that a human reads.

Formatting dates and times

The %t specifier (or %T for upper case) with a suffix prints part of a date or time: %tY is the year, %tm the month, %td the day, %tB the month name, %tH:%tM hours and minutes, %tF the ISO date. The argument can be a LocalDate, LocalDateTime, Date, Calendar or long.

import java.time.LocalDate;
import java.util.Locale;

public class DateFormatExample {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2026, 10, 16);
        System.out.println(String.format("%tF", date));
        System.out.println(String.format("%1$tm/%1$td/%1$tY", date));
        System.out.println(String.format(Locale.US, "%1$tB %1$te, %1$tY", date));
    }
}
// Output:
// 2026-10-16
// 10/16/2026
// October 16, 2026

Note the %1$: without the index, every %t specifier expects its own argument, and the template "%tm/%td/%tY" with a single date throws MissingFormatArgumentException. For complex date patterns, modern code usually reaches for java.time.format.DateTimeFormatter.

Common mistakes and the exceptions they throw

The compiler does not check format strings: mistakes surface only at runtime as unchecked exceptions, subclasses of IllegalFormatException (which extends IllegalArgumentException). Here are the most frequent cases and how to fix them:

Code What happens How to fix it
format("%d", 3.5) IllegalFormatConversionException: d != java.lang.Double Use %f for floating-point values or cast with (int)
format("%s and %s", "A") MissingFormatArgumentException: Format specifier '%s' Pass all arguments or reuse one with %1$s
format("%-05d", 7) IllegalFormatFlagsException: Flags = '-0' Pick one flag: %-5d or %05d
format("%i", 7) (a habit from C) UnknownFormatConversionException: Conversion = 'i' Java prints integers only with %d
format("Save 50%") UnknownFormatConversionException: Conversion = '%' Escape the percent sign: 50%%
format("%s", "a", "b") No exception, output a — the extra argument is silently ignored Match the number of specifiers and arguments (IDEs highlight this)
format("%d", (Integer) null) No exception, output null Check the value for null beforehand

%n or \n?

Use %n in templates: it inserts the line separator of the current OS (\r\n on Windows, \n on Linux and macOS). \n always produces a single LF character — correct only when a file format or protocol requires exactly that.

DecimalFormat and MessageFormat

Besides String.format(), the java.text package offers two formatting classes: DecimalFormat formats a number by a pattern such as #,##0.00, and MessageFormat inserts arguments by number in curly braces {0}, {1} — the approach used in UI localization bundles.

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

public class TextFormatExample {
    public static void main(String[] args) {
        DecimalFormat df = new DecimalFormat("#,##0.00",
                DecimalFormatSymbols.getInstance(Locale.US));
        System.out.println(df.format(1234567.891));

        System.out.println(MessageFormat.format("File {0} contains {1} lines", "data.txt", 1500));
        System.out.println(MessageFormat.format("File {0} contains {1,number,#} lines", "data.txt", 1500));
    }
}
// Output:
// 1,234,567.89
// File data.txt contains 1,500 lines
// File data.txt contains 1500 lines

A MessageFormat quirk: numbers in {1} are formatted with the locale's grouping separators, so 1500 becomes 1,500. The {1,number,#} format prints the number without separators. Another one: a single quote in a MessageFormat pattern is an escape character, so an apostrophe has to be doubled — "Don''t" prints Don't.

Frequently Asked Questions

Is there a sprintf equivalent in Java?

Yes: String.format() (or "template".formatted() in Java 15+) returns the formatted string, and System.out.printf() corresponds to C's printf. The syntax is similar but not identical: there is no %i or %u, %n is the line separator, and %b accepts any object. More in the section Common mistakes and the exceptions they throw.

What is the difference between printf() and println()?

println() prints a single value and always adds a line break. printf() takes a template and any number of arguments, but adds no line break on its own — you have to put %n into the template.

Which is faster: String.format() or concatenation with +?

Concatenation is noticeably faster: every String.format() call parses the template and creates a Formatter. In loops and hot code, build strings with + or a StringBuilder, and keep String.format() for places where readability and alignment matter. All concatenation options are compared in Java String Concatenation.

How do I keep two decimal places without rounding?

%.2f always rounds with HALF_UP: String.format("%.2f", 1.999) gives 2.00. To drop the extra digits instead, use BigDecimal: new BigDecimal("1.999").setScale(2, RoundingMode.DOWN) returns 1.99.

Comments

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