String Formatting in Java Explained — printf, format, and String.formatted()
Every real application talks to humans — through log messages, receipts, error alerts, or dashboards. The moment your app needs to say something like 'Welcome back, Sarah! You have 3 unread messages,' you have a choice: stitch it together with concatenation (+), or use string formatting to build it elegantly in one shot. The difference between the two is the difference between hand-writing every letter and using a mail merge template.
The problem with concatenation is that it gets ugly fast. Mixing variables and text with + signs is hard to read, easy to mess up, and a nightmare when you need consistent spacing or decimal precision. What if you need a price to always show two decimal places? What if you're building a table and every column needs to be exactly 10 characters wide? Concatenation simply can't do that without a lot of extra code. String formatting was built to solve exactly these problems.
By the end of this article you'll know three ways to format strings in Java — printf(), String.format(), and the newer String.formatted() — when to use each one, how format specifiers work, how to control decimal places and column widths, and the mistakes that trip up nearly every beginner. You'll also be ready to answer the string formatting questions that come up in Java interviews.
Why Concatenation Breaks Down (And What Format Specifiers Are)
Before we write a single line of formatting code, let's feel the pain that makes formatting necessary. Suppose you want to print a price tag for a product. With concatenation you'd write something like: 'Price: $' + price. That works fine until price is 9.9 — suddenly you get 'Price: $9.9' instead of 'Price: $9.90'. You'd need extra code to fix the rounding. And if you're printing a table of 20 products, aligning the columns becomes a nightmare.
String formatting solves this with a format string — a template that contains special placeholders called format specifiers. A format specifier starts with a percent sign (%) and tells Java two things: what kind of value goes here (text, integer, decimal?) and optionally how to display it (how wide? how many decimal places?).
The most important specifiers to memorise right now are: %s for any String, %d for whole numbers (integers), and %f for decimal numbers (floats and doubles). Think of % as a blank slot in your template. When Java sees %s it reaches into your argument list and drops a string into that slot. That's the whole mental model — a template with typed blank slots.
public class WhyConcatenationFails { public static void main(String[] args) { String productName = "Wireless Mouse"; double price = 9.9; // A price with only one decimal place // --- The concatenation approach (messy and imprecise) --- // This prints $9.9 instead of the expected $9.90 System.out.println("Concatenation result: Price: $" + price); // --- The formatting approach (clean and precise) --- // %s is replaced by productName // %.2f means: a decimal number, always show exactly 2 decimal places System.out.printf("Formatted result: %s costs $%.2f%n", productName, price); // Let's also show %d with a whole number int itemsInStock = 42; // %d is replaced by the integer value of itemsInStock System.out.printf("Stock: %d units available%n", itemsInStock); } }
Formatted result: Wireless Mouse costs $9.90
Stock: 42 units available
The Three Ways to Format Strings in Java — printf, String.format, and String.formatted
Java gives you three tools for string formatting, and they all use the same format specifier syntax. The difference is what they do with the result.
System.out.printf() prints the formatted text directly to the console. It doesn't return anything — it just fires the output. Think of it as format-and-print in one step. It's perfect for logging and console output.
String.format() does the same formatting work but returns the finished string instead of printing it. This is the one you want when you need to store the result, pass it to another method, add it to a list, or send it as an HTTP response. It's the workhorse of the three.
String.formatted() was introduced in Java 15. It's called on the format string itself, so it reads slightly more naturally. Instead of String.format("Hello %s", name) you write "Hello %s".formatted(name). Under the hood it does exactly the same thing as String.format(). It's a stylistic preference — newer codebases often prefer it for readability.
All three share the same format specifiers, so once you know %s, %d, %f and the width/precision tricks, you can use any of the three interchangeably.
public class ThreeFormattingApproaches { public static void main(String[] args) { String customerName = "Marcus"; int orderNumber = 10045; double totalAmount = 134.5; // --- Approach 1: System.out.printf() --- // Prints directly. Returns void. Use this for console output. System.out.printf("Order #%d for %s — Total: $%.2f%n", orderNumber, customerName, totalAmount); // --- Approach 2: String.format() --- // Returns a String. Store it, pass it, use it anywhere. String emailSubject = String.format("Your order #%d is confirmed!", orderNumber); System.out.println("Email subject: " + emailSubject); // You can also store a receipt line and reuse it String receiptLine = String.format("Customer: %-15s | Amount: $%8.2f", customerName, totalAmount); // %-15s = left-align the name in a 15-character wide column // %8.2f = right-align the number in an 8-character wide column, 2 decimal places System.out.println(receiptLine); // --- Approach 3: String.formatted() (Java 15+) --- // Called on the template string itself. Same result as String.format(). String welcomeMessage = "Welcome back, %s! You have %d new notifications." .formatted(customerName, 3); System.out.println(welcomeMessage); } }
Email subject: Your order #10045 is confirmed!
Customer: Marcus | Amount: $ 134.50
Welcome back, Marcus! You have 3 new notifications.
Controlling Width, Alignment and Decimal Precision Like a Pro
The real power of format specifiers isn't just swapping in values — it's controlling exactly how those values look. Three modifiers do most of the heavy lifting: width, precision, and the minus sign for alignment.
Width is a number you put between % and the type letter. %10d means 'print this integer in a space that is at least 10 characters wide.' Java right-aligns by default and pads with spaces on the left. This is how you make neat tables without any extra effort.
Precision is the .number part before f. %.2f means 'show exactly two digits after the decimal point.' Java will round the value for you. No manual Math.round() needed.
The minus sign (-) switches alignment to left. %-10s means 'print this string, left-aligned, in a 10-character wide slot.' You'd use this for names or labels on the left side of a table while keeping numbers right-aligned on the right.
Combining width and precision — like %10.2f — gives you a number that is right-aligned in a 10-character column with exactly 2 decimal places. That one specifier replaces about five lines of manual padding code.
public class FormattedReceiptPrinter { public static void main(String[] args) { // Simulating a simple store receipt with aligned columns System.out.println("============================================"); System.out.printf("%-20s %8s %10s%n", "Item", "Qty", "Price"); // %-20s = Item name, left-aligned, 20 chars wide // %8s = Qty header, right-aligned, 8 chars wide // %10s = Price header, right-aligned, 10 chars wide System.out.println("--------------------------------------------"); // Each row uses the same column widths for perfect alignment printReceiptRow("Organic Coffee", 2, 14.99); printReceiptRow("Sourdough Bread", 1, 5.5); printReceiptRow("Almond Milk", 3, 3.79); printReceiptRow("Dark Chocolate", 4, 2.25); System.out.println("============================================"); double tax = 2.76; double total = 59.09; // Right-align the totals to match the price column System.out.printf("%-20s %8s %10.2f%n", "Tax (5%)", "", tax); System.out.printf("%-20s %8s %10.2f%n", "TOTAL", "", total); System.out.println("============================================"); } static void printReceiptRow(String itemName, int quantity, double unitPrice) { double lineTotal = quantity * unitPrice; // %-20s = item name left-aligned in 20 chars // %8d = quantity right-aligned in 8 chars // %10.2f = price right-aligned in 10 chars, 2 decimal places System.out.printf("%-20s %8d %10.2f%n", itemName, quantity, lineTotal); } }
Item Qty Price
--------------------------------------------
Organic Coffee 2 29.98
Sourdough Bread 1 5.50
Almond Milk 3 11.37
Dark Chocolate 4 9.00
============================================
Tax (5%) 2.76
TOTAL 59.09
============================================
Common Mistakes, Gotchas, and Interview Questions
Even experienced developers make a handful of predictable errors with string formatting. Knowing them ahead of time saves you twenty minutes of frustrated debugging.
The most dangerous mistake is a type mismatch between the specifier and the argument. If your format string has %d but you pass a double, Java throws a java.util.MisformatConversionException at runtime — not at compile time. Java can't catch this for you until the code actually runs, so it's easy to miss in testing.
The second common trap is forgetting that printf() returns void. New developers sometimes write String result = System.out.printf(...) and are confused when the compiler refuses. Use String.format() whenever you need the result as a string.
The third mistake is using inside a format string on Windows and getting odd line break behaviour. Always use %n inside format strings — it's the portable choice.
Finally, argument count mismatches are a classic runtime headache: if your format string has three specifiers but you only pass two arguments, Java throws a MissingFormatArgumentException. Count your placeholders and count your arguments — they must match.
public class FormattingMistakesFixed { public static void main(String[] args) { double temperature = 36.6; // --- MISTAKE 1: Type mismatch --- // WRONG: %d expects an integer, but temperature is a double // This compiles fine but crashes with MisformatConversionException at runtime // System.out.printf("Temp: %d degrees%n", temperature); // DON'T DO THIS // FIXED: Use %f (or %.1f for one decimal place) for doubles System.out.printf("Temp: %.1f degrees%n", temperature); // --- MISTAKE 2: Assigning printf() to a variable --- // WRONG: printf() returns void — this won't compile // String output = System.out.printf("Hello %s%n", "Maya"); // COMPILER ERROR // FIXED: Use String.format() when you need the result as a value String output = String.format("Hello %s!", "Maya"); System.out.println(output); // --- MISTAKE 3: Argument count mismatch --- // WRONG: Two specifiers, only one argument — MissingFormatArgumentException at runtime // System.out.printf("%s scored %d points", "Lena"); // CRASHES // FIXED: Match the number of arguments to the number of specifiers exactly System.out.printf("%s scored %d points%n", "Lena", 98); // --- MISTAKE 4: Using \n instead of %n inside format strings --- // This works on Mac/Linux but can behave oddly on Windows // System.out.printf("Line one\nLine two"); // Fragile // FIXED: Always use %n inside printf/format strings System.out.printf("Line one%nLine two%n"); } }
Hello Maya!
Lena scored 98 points
Line one
Line two
| Feature / Aspect | System.out.printf() | String.format() | String.formatted() (Java 15+) |
|---|---|---|---|
| Returns a value? | No — returns void | Yes — returns String | Yes — returns String |
| Prints to console? | Yes — directly | No — you print it yourself | No — you print it yourself |
| Where to use it? | Quick console/debug output | Business logic, APIs, logs | Same as String.format(), cleaner syntax |
| Introduced in Java version | Java 1.5 | Java 1.5 | Java 15 |
| Syntax style | System.out.printf(template, args) | String.format(template, args) | template.formatted(args) |
| Same format specifiers? | Yes | Yes | Yes |
| Thread-safe? | Yes | Yes | Yes |
| Best for beginners? | Good for learning | Most versatile — learn this first | Great once on Java 15+ |
🎯 Key Takeaways
- Format specifiers are typed placeholders: %s for strings, %d for integers, %f for decimals — mixing them up causes a runtime crash, not a compile error, so test every format path.
- printf() prints and vanishes — it returns void. String.format() and String.formatted() return the finished String so you can store, pass, or manipulate it. Pick based on what you need to DO with the result.
- Width and alignment turn raw output into readable tables for free: %-20s left-aligns text in 20 characters, %10.2f right-aligns a decimal in 10 characters with 2 decimal places — no manual padding code needed.
- Always use %n instead of \n inside format strings — it produces the correct line ending for the current operating system, making your code portable across Windows, Mac, and Linux without any extra effort.
⚠ Common Mistakes to Avoid
- ✕Mistake 1: Using %d for a double or float — Your code compiles fine but crashes with java.util.MisformatConversionException when it runs because %d only accepts integer types. Fix: Use %f for any double or float, and optionally add precision like %.2f to control decimal places.
- ✕Mistake 2: Trying to assign the result of System.out.printf() to a String variable — The compiler rejects this because printf() returns void. Fix: Switch to String.format() or String.formatted() whenever you need the formatted result as a String you can store or pass around.
- ✕Mistake 3: Providing fewer arguments than format specifiers — Java throws MissingFormatArgumentException at runtime with a message like 'Format specifier %d'. Fix: Count every % specifier in your template (excluding %n and %%) and make sure you pass exactly that many arguments after the template string.
Interview Questions on This Topic
- QWhat is the difference between System.out.printf() and String.format() in Java, and when would you choose one over the other?
- QWhat happens at runtime if the type of an argument doesn't match its format specifier — for example, passing a double where %d is used? Can the compiler catch this?
- QHow would you format a column of monetary values in a console report so that they are all right-aligned with exactly two decimal places, regardless of how many digits the number has?
Frequently Asked Questions
What is the difference between String.format() and String.formatted() in Java?
They produce identical output — both return a formatted String using the same format specifiers. The only difference is syntax: String.format() is a static method called as String.format(template, args), while String.formatted() is an instance method called on the template string itself as template.formatted(args). String.formatted() was added in Java 15 and is often considered more readable. If you're on Java 14 or earlier, stick with String.format().
When should I use string formatting instead of string concatenation with + in Java?
Use formatting whenever you need consistent structure, controlled decimal places, fixed column widths, or more than two variables in a single string. Concatenation with + is fine for simple one-off combinations, but it scales poorly and can't handle alignment or number precision without extra code. A good rule: if your string has more than one variable in it, formatting is almost always cleaner.
Why does Java throw an exception at runtime for a bad format specifier instead of catching it at compile time?
Because Java evaluates format strings dynamically — the format string is just a regular String as far as the compiler is concerned. The compiler has no way to inspect the content of a String at compile time and check it against the arguments you pass. The validation only happens when the code actually runs and the formatting engine parses the template. This is why it's important to write tests that exercise every code path containing a format string.
Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.