Java gives you two ways to build formatted text. String.format takes a template with placeholders and arguments, and text blocks give you a multi-line string that keeps the indentation of your source code. Together they cover the two things you constantly need: lining values up into columns, and writing text that spans several lines without a pile of escape characters.
How does String.format work?
The first argument is a template. A percent sign introduces a conversion, and the characters after it describe how to render the corresponding argument. %s means "any object as text", %d means "a whole number", %f means "a decimal number", %c means "a character" and %b means "a boolean". The arguments are consumed in order.
public class FormatBasics {
public static void main(String[] args) {
System.out.println(String.format("%s scored %d points", "ana", 42));
System.out.println(String.format("%s scored %d points", "bo", 7));
System.out.println(String.format("ratio: %.2f", 3.14159));
System.out.println(String.format("padded: [%6s]", "hi"));
System.out.println(String.format("left aligned: [%-6s]", "hi"));
System.out.println(String.format("percent: 90%%"));
System.out.println(String.format("hex: %x and octal: %o", 255, 8));
System.out.println(String.format("boolean: %b, char: %c", true, 'J'));
}
}ana scored 42 points
bo scored 7 points
ratio: 3.14
padded: [ hi]
left aligned: [hi ]
percent: 90%
hex: ff and octal: 10
boolean: true, char: JTwo of those conversions earn special attention. %% prints a literal percent sign, which is why the template for a percentage reads "90%%" and not "90%". And %s is the safe default, because it works with any object - including null, which prints as the word null rather than throwing.
How do you control width and precision?
Between the percent sign and the conversion letter you can add a width, a precision and flags. The width pads the output to a minimum number of characters; the precision limits how many digits appear after the decimal point. A dash makes the padding go on the right instead of the left, which is what you want for left-aligned columns.
public class TextBlocks {
public static void main(String[] args) {
String json = """
{
"name": "tutorsbot",
"active": true
}
""";
System.out.println(json);
System.out.println("lines : " + json.lines().count());
String query = """
SELECT id, email
FROM members
WHERE status = 'active'
""";
System.out.println(query);
}
}{
"name": "tutorsbot",
"active": true
}
lines : 4
SELECT id, email
FROM members
WHERE status = 'active'The %08.3f line shows width and precision combined: eight characters wide, three decimal places, with zeros filling the gap. %,d inserts grouping separators, which is the difference between 1234567 and 1,234,567. The last line shows a date conversion set, where %tY is the four-digit year, %tm the two-digit month and %td the two-digit day.
One thing to know about %f and numbers: the decimal separator follows the default locale, which is why the German line prints a comma. When you want a fixed format regardless of where the program runs, pass an explicit Locale as the first argument.
How are printf, format and formatted related?
They are three faces of the same mechanism. System.out.printf writes straight to standard output using a format template. String.format returns the formatted text as a new string. And formatted is a method on every object, so you can format any value without converting it to a string first.
Use printf when you are printing, String.format when you need the text as a value, and formatted when you are building a message from an object you already have.
What are text blocks and why are they useful?
A text block is a string literal that starts with three double quotes and can run over several lines. The compiler strips the common indentation of the closing delimiter, so you can lay the text out inside your source code and get back a clean string with no leading spaces. That makes text blocks the natural choice for JSON, SQL, HTML fragments and any other embedded text.
import java.util.Locale;
public class FormatWidth {
public static void main(String[] args) {
System.out.println(String.format("%08.3f", 3.14159));
System.out.println(String.format("%+d", 250));
System.out.println(String.format("%,d", 1234567));
System.out.println(String.format("%,.2f", 1234.5));
System.out.println(String.format("name: %-10s | id: %04d", "ada", 27));
System.out.println(String.format(Locale.GERMANY, "%.2f", 1234.5));
java.util.Date today = new java.util.Date();
System.out.println(String.format("date: %tY-%tm-%td", today, today, today));
}
}0003.142
+250
1,234,567
1,234.50
name: ada | id: 0027
1234,50
date: 2026-10-27Look at the JSON output: the braces sit at column zero even though the lines are indented inside the method. That is the incidental-whitespace rule at work - the indentation common to every line and to the closing delimiter is removed. The lines() count confirms the block became three lines of real content.
What are the rules that trip people up?
- The opening delimiter must be followed by a line break; content starts on the next line.
- The closing delimiter's indentation sets the baseline that gets stripped from every line.
- A trailing line break before the closing delimiter is removed automatically.
- Escape sequences still work:
\n,\tand\"are interpreted as usual. - Use
\sto keep significant whitespace on a line that would otherwise be stripped.
The last two lines of the fourth program show how a text block behaves at its edges: a closing delimiter on its own line does not add a trailing line break, so the string holds exactly hello and the line count is one.
How do you combine formatting with text blocks?
A text block is a string, so it has all the string methods. The one you want is formatted, which takes the template placeholders and arguments just like String.format does.
public class TextBlockFormat {
public static void main(String[] args) {
String name = "ana";
int count = 3;
String report = """
Member: %s
Items: %d
Status: %s
""".formatted(name, count, "active");
System.out.println(report);
String lines = """
one
two
three
""";
lines.lines().forEach(line -> System.out.println("[" + line + "]"));
String closing = """
hello
""";
System.out.println("closed = [" + closing + "]");
System.out.println("lines in it : " + closing.lines().count());
}
}Member: ana
Items: 3
Status: active
[one]
[two]
[three]
closed = [hello
]
lines in it : 1That gives you a report template that lives in your source code, readable and aligned, without any concatenation or escaping. Because formatted is a method on the string, the same call works on any value, which is handy when the template itself comes from a variable.
The practical tips
- Use
%sfor anything you are not certain about - it never throws. - Give every column a fixed width so the output lines up.
- Use
%%for a literal percent sign. - Pass an explicit
Localewhen you format money or dates. - Prefer a text block over a string with embedded
\nwhenever the text spans lines. - Call
formattedon a text block to fill it in place.
Try it yourself: write a small receipt printer that takes an item name, a quantity and a unit price and prints a line per item with the name left-aligned in twenty columns and the price right-aligned in ten with two decimals, followed by a total line. Format the numbers with String.format, build the whole receipt inside a text block, and print it. Check that the columns line up when you run it in a terminal.