13. Formatting and Localizing in Java
Table of Contents
- 13.1 String Formatting
- 13.2 Number Formatting
- 13.2.1 NumberFormat
- 13.2.2 Localizing Numbers
- 13.2.3 DecimalFormat and NumberFormat
- 13.2.4 DecimalFormat Pattern Structure
- 13.2.5 The 0 Symbol Mandatory Digit
- 13.2.6 The # Symbol Optional Digit
- 13.2.7 Combining 0 and #
- 13.2.8 Decimal and Grouping Separators
- 13.2.9 DecimalFormatSymbols Locale-Specific Formatting Symbols
- 13.2.10 Special DecimalFormat Patterns
- 13.2.11 Common Rules and Pitfalls
- 13.3 Parsing Numbers
- 13.4 Date and Time Formatting
- 13.5 Internationalization i18n and Localization l10n
- 13.6 Properties and Resource Bundles
- 13.7 Common Rules and Pitfalls
This chapter delivers a deep and practical treatment of formatting in Java 21.
13.1 String Formatting
13.1.1 String.format and formatted
String.format() creates formatted strings using printf-style placeholders.
It is locale-sensitive and returns a new immutable String.
String result = String.format("The User: %s | Score: %d", "Bob", 42);
System.out.println(result);
// Or
System.out.println("The User: %s | Score: %d".formatted("Bob", 42));
Output:
The User: Bob | Score: 42
Key characteristics:
- Uses format specifiers like
%s(any type, commonly String values),%d(integral values),%f(floating-point values). - Does not modify existing strings.
- Throws
IllegalFormatExceptionif arguments mismatch the format. - Is locale-sensitive when a
Localeis provided.
String price = String.format(Locale.GERMANY, "%.2f", 1234.5);
// Output (German locale): 1234,50
13.1.1.1 Floating-point Flags
%f is used to format floating-point numbers (float, double, BigDecimal) using decimal notation.
System.out.printf("%f", 12.345);
12.345000
- Always prints 6 digits after the decimal point by default.
- Uses rounding (not truncation).
- Is locale-sensitive for the decimal separator.
13.1.1.2 Precision (.n)
Precision defines the number of digits printed after the decimal point.
System.out.printf("%.2f", 12.345);
12.35
%.0fprints no decimal digits.- Rounding is applied.
- Precision is applied before width padding.
13.1.1.3 Width (m)
Width defines the minimum total number of characters in the output.
System.out.printf("%8.2f", 12.34);
12.34
- Pads with spaces by default.
- If the value is longer, width is ignored (it never truncates).
- Padding is applied on the left by default.
13.1.1.4 Zero Padding 0 Flag
The 0 flag replaces space padding with zeros.
System.out.printf("%08.2f", 12.34);
00012.34
- Requires a width.
- Zeros are inserted after the sign.
- Ignored if left-justified (
-flag).
13.1.1.5 Left Justification - Flag
The - flag left-aligns the value within the width.
System.out.printf("%-8.2f", 12.34);
12.34
- Padding is moved to the right.
- Overrides zero padding.
13.1.1.6 Explicit Sign + Flag
The + flag forces display of the sign for positive numbers.
System.out.printf("%+8.2f", 12.34);
+12.34
- Negative numbers already show
-. - Overrides the space flag (which prints a leading space for positive values).
13.1.1.7 Parentheses for Negatives ( Flag
The ( flag formats negative numbers using parentheses.
System.out.printf("%(8.2f", -12.34);
(12.34)
- Only affects negative values.
- Rarely used in practice.
13.1.1.8 Combining Flags
System.out.printf("%+010.2f", 12.34);
+000012.34
Evaluation order (semplificato):
- Precision is applied.
- Sign is handled.
- Width is enforced.
- Padding (spaces or zeros) is applied.
13.1.1.9 Locale Effects
System.out.printf(Locale.FRANCE, "%,.2f", 12345.67);
12 345,67
Decimal and grouping separators depend on the active Locale.
13.1.1.10 Common Pitfalls
%fdefaults to 6 decimal places if no precision is specified.- Width never truncates output, it only pads if needed.
0flag is ignored when-is present.+overrides the space flag.- Grouping and separators are Locale-dependent.
13.1.2 Custom Text Values and Escaping
Certain characters have special meaning in format strings and must be escaped.
%%→ literal percent sign.\n,\t→ standard Java escapes.
String msg = String.format("Completion: %d%%%nStatus: OK", 100);
System.out.println(msg);
Output:
Completion: 100%
Status: OK
Note
A single % without a valid specifier causes an IllegalFormatException at runtime.
13.2 Number Formatting
13.2.1 NumberFormat
NumberFormat is an abstract class used to format and parse numbers in a locale-aware manner.
NumberFormat nf = NumberFormat.getInstance(Locale.FRANCE);
System.out.println(nf.format(1234567.89));
Important
- Factory methods determine formatting style (general, integer, currency, percent, compact, ...).
- Formatting depends on the provided
Locale. NumberFormat(andDecimalFormat) are not thread-safe.
13.2.2 Localizing Numbers
Number localization affects decimal separators, grouping separators, and currency symbols.
NumberFormat nfUS = NumberFormat.getInstance(Locale.US);
NumberFormat nfIT = NumberFormat.getInstance(Locale.ITALY);
System.out.println(nfUS.format(1234.56)); // 1,234.56
System.out.println(nfIT.format(1234.56)); // 1.234,56
13.2.3 DecimalFormat and NumberFormat
DecimalFormat is a concrete subclass of NumberFormat that provides fine-grained control over numeric formatting using patterns.
NumberFormat defines locale-aware formatting via factory methods, while DecimalFormat allows explicit pattern-based control.
NumberFormat nf = NumberFormat.getInstance(Locale.US);
DecimalFormat df = (DecimalFormat) nf;
Or directly:
DecimalFormat df = new DecimalFormat("#,##0.00");
Note
DecimalFormatis mutable (you can change pattern, symbols, etc.).DecimalFormatis not thread-safe.- Formatting is locale-sensitive via
DecimalFormatSymbols.
13.2.4 DecimalFormat Pattern Structure
A pattern may contain a positive and an optional negative subpattern, separated by ;.
#,##0.00;(#,##0.00)
Note
- First part → positive numbers.
- Second part → negative numbers.
- If the negative part is omitted, negative numbers use a leading
-automatically.
13.2.5 The 0 Symbol (Mandatory Digit)
The 0 symbol forces a digit to appear, padding with zeros if necessary.
DecimalFormat df = new DecimalFormat("0000.00");
System.out.println(df.format(12.3));
0012.30
- Controls the minimum number of digits.
- Pads with zeros if the number has fewer digits.
- Useful for fixed-width or aligned output.
13.2.6 The # Symbol (Optional Digit)
The `` symbol displays a digit only if it exists.
DecimalFormat df = new DecimalFormat("####.##");
System.out.println(df.format(12.3));
12.3
- Suppresses leading zeros.
- Suppresses unnecessary trailing zeros.
- Good for “human-friendly” formatting.
13.2.7 Combining 0 and #
Patterns often combine both symbols for flexibility.
DecimalFormat df = new DecimalFormat("#,##0.##");
System.out.println(df.format(12));
System.out.println(df.format(12.5));
System.out.println(df.format(12345.678));
12
12.5
12,345.68
Pattern explanation:
#,##0 . ##
^ ^ ^
| | |
| | └─ optional fractional digits (#)
| └───── mandatory integer digit (0)
└──────── grouping pattern (,)
- At least one integer digit is guaranteed (the
0). - Digits are grouped by thousands using the grouping separator.
- Fractional digits are optional (up to two).
13.2.8 Decimal and Grouping Separators
In patterns:
.→ decimal separator.,→ grouping separator.
The actual symbols used at runtime depend on the Locale (for example, comma vs dot).
13.2.9 DecimalFormatSymbols Locale-Specific Formatting Symbols
DecimalFormatSymbols symbols =
DecimalFormatSymbols.getInstance(Locale.FRANCE);
DecimalFormat df =
new DecimalFormat("#,##0.00", symbols);
System.out.println(df.format(1234.5));
1 234,50
- Controls decimal and grouping separators.
- Controls minus sign and currency symbol.
- Controls NaN and Infinity strings.
13.2.10 Special DecimalFormat Patterns
0.###E0 scientific notation
###% percent
¤#,##0.00 currency (¤ is the currency sign)
13.2.11 Common Rules and Pitfalls
DecimalFormatis aNumberFormatsubclass.0forces digits,#does not.- Patterns control formatting, not the rounding mode itself (use
setRoundingMode()). - Grouping only works if the grouping separator (usually
,) is present in the pattern. - Parsing may succeed partially without error if trailing characters appear after a valid number.
DecimalFormatis mutable and not thread-safe.
13.3 Parsing Numbers
Parsing converts localized text into numeric values.
By default, parsing is lenient.
NumberFormat nf = NumberFormat.getInstance(Locale.FRANCE);
Number n = nf.parse("12 345,67abc"); // parses 12345.67
- Parsing stops at the first invalid character.
- Trailing text is ignored unless explicitly checked.
13.3.1 Parsing with DecimalFormat
DecimalFormat can also parse numbers. Parsing is lenient by default.
DecimalFormat df = new DecimalFormat("#,##0.##");
Number n = df.parse("1,234.56abc");
- Parsing stops at the first invalid character.
- Trailing text is ignored if present.
To enforce strict parsing:
df.setParseStrict(true);
13.3.2 CompactNumberFormat
Compact formatting shortens large numbers for human readability.
- Supports SHORT vs LONG styles.
- Uses locale-dependent abbreviations (for example, K, M, “million”).
NumberFormat cnf =
NumberFormat.getCompactNumberInstance(
Locale.US, NumberFormat.Style.SHORT);
System.out.println(cnf.format(1_200)); // 1.2K
System.out.println(cnf.format(5_000_000)); // 5M
NumberFormat cnf1 =
NumberFormat.getCompactNumberInstance(
Locale.US, NumberFormat.Style.SHORT);
NumberFormat cnf2 =
NumberFormat.getCompactNumberInstance(
Locale.US, NumberFormat.Style.LONG);
System.out.println(cnf1.format(315_000_000)); // 315M
System.out.println(cnf2.format(315_000_000)); // 315 million
13.4 Date and Time Formatting
13.4.1 DateTimeFormatter
Java 21 relies on java.time and DateTimeFormatter for modern date/time formatting.
DateTimeFormatter f =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
System.out.println(LocalDateTime.now().format(f));
Core properties:
- Immutable.
- Thread-safe.
- Locale-aware.
13.4.2 Standard Date/Time Symbols
y year
M month number (or name with more letters)
d day of month
E day name
H hour (0–23)
h hour (1–12)
m minute
s second
a AM/PM marker
z time zone
13.4.3 datetime.format vs formatter.format
Both methods are functionally identical:
date.format(formatter);
formatter.format(date);
date.format(formatter)→ preferred for readability (data first, then formatting).formatter.format(date)→ sometimes convenient in functional or reusable formatter code.
13.4.4 Localizing Dates
Localized styles adapt date output to cultural norms.
DateTimeFormatter fullIt =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.FULL)
.withLocale(Locale.ITALY);
DateTimeFormatter shortIt =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.SHORT)
.withLocale(Locale.ITALY);
LocalDate today = LocalDate.of(2025, 12, 17);
System.out.println(today.format(fullIt));
System.out.println(today.format(shortIt));
Possible output:
mercoledì 17 dicembre 2025
17/12/25
13.5 Internationalization (i18n) and Localization (l10n)
13.5.1 Locales
A Locale defines language, country, and optional variant.
Locale l1 = Locale.US;
Locale l2 = Locale.of("fr", "FR");
Locale l3 = new Locale.Builder()
.setLanguage("en")
.setRegion("US")
.build();
Locale formats:
en(it, fr, etc.): lowercase language code.en_US(fr_CA, it_IT, etc.): lowercase language code + underscore + uppercase country code.
13.5.2 Locale Categories
Locale categories separate formatting from UI language.
Locale.Category lets Java use different default locales for different purposes.
There are two categories:
| Category | Used for |
|---|---|
| FORMAT | Numbers, dates, currency, other formatting |
| DISPLAY | Human-readable text (UI, names, messages) |
13.5.3 Real-world Example
A French user living in Germany might want:
- Numbers and dates → German format.
- UI language → French.
Before Java 7, this was not possible.
Locale.setDefault(Locale.Category.FORMAT, Locale.GERMANY);
Locale.setDefault(Locale.Category.DISPLAY, Locale.FRANCE);
Sample effects:
| Aspect | Result (example) |
|---|---|
| Numbers | 1.234,56 |
| Dates | 31.12.2025 |
| Currency | € |
| UI text | French |
| Month names | décembre |
| Country names | Allemagne |
13.6 Properties and Resource Bundles
Resource bundles externalize text and allow localization without code changes.
ResourceBundle rb =
ResourceBundle.getBundle("messages", Locale.GERMAN);
String msg = rb.getString("welcome");
13.6.1 Resource Bundle Resolution Rules
Java searches bundles in a strict fallback order. For example, with base name messages and locale de_DE:
- messages_de_DE.properties
- messages_de.properties
- messages.properties
If none is found → MissingResourceException.
Note
Traditional .properties files are specified as ISO-8859-1; non-ASCII characters must be encoded as Unicode escapes (for example, \u00E9 for é) unless you use alternate loading mechanisms.
13.7 Common Rules and Pitfalls
DateTimeFormatteris immutable and thread-safe.NumberFormat/DecimalFormatare mutable and not thread-safe.- Changing the
Localeaffects how values are formatted and parsed, not the underlying numeric or temporal values. - Parsing with
NumberFormatorDecimalFormatmay succeed partially without throwing if extra text follows a valid number. java.timereplaces most uses of the oldjava.util.Date/CalendarAPIs in modern code and in the exam.