DateTimeFormatter.ofPattern()
Build a formatter from a pattern such as dd MMM uuuu, pass a Locale, and avoid the pattern-letter mix-ups that print the wrong year.
public static DateTimeFormatter ofPattern(String pattern)
public static DateTimeFormatter ofPattern(String pattern, Locale locale)- Added in Java Java 8
Parameters
| Parameter | Type | Description |
|---|---|---|
$pattern | String | The pattern, built from letters such as uuuu, MM, dd, HH, mm, ss, EEEE and MMM, with literal text in single quotes. |
$localeoptional | Locale= Locale.getDefault(Locale.Category.FORMAT) | The locale for month and day names and other localised text. |
Return value
DateTimeFormatterAn immutable, thread-safe formatter for the pattern, using the default FORMAT locale or the given locale.
Errors
IllegalArgumentException if the pattern is invalid.
Changelog
- PHP Java 9CLDR locale data became the default (JEP 252), so some localised names and formats differ from Java 8.
- PHP Java 16Added the pattern letter B for day periods such as "in the morning".
DateTimeFormatter.ofPattern("dd MMM uuuu") builds a formatter from a pattern string. Use it with date.format(formatter) to produce text and with LocalDate.parse(text, formatter) to read it. Formatters are immutable and thread-safe, so declare them once as constants. That alone fixes a whole class of bugs from the old SimpleDateFormat, which was not safe to share between request threads.
Pattern letters that bite
| Letter | Means | Common mix-up |
|---|---|---|
MM | month | mm is minutes |
HH | hour 0-23 | hh is hour 1-12, needs a |
uuuu / yyyy | year / year of era | YYYY is week-based year |
dd | day of month | DD is day of year |
YYYY is the nastiest: it prints the year the week belongs to, so for a few days around New Year it prints the wrong year. It passes every test written in June. Which week counts as the first one also depends on the locale.
Always pass a Locale
Text fields such as MMM (Jan), MMMM (January) and EEEE (Monday) use the JVM's default locale unless you pass one. A container built with a different default locale will print different month names. For machine-readable output pass Locale.ROOT or Locale.ENGLISH; for user-facing text pass the user's locale from the request.
Since Java 9 the locale data comes from CLDR, so some localised text differs from Java 8. If a test compares localised output to a hard-coded string, that string came from a particular JDK version.
When not to use a pattern
For APIs, logs and databases, use ISO-8601 through the built-in constants such as DateTimeFormatter.ISO_LOCAL_DATE or just toString(). Patterns are for humans: emails, PDFs, CSV files a user opens in a spreadsheet. For localised display, DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM) with a locale lets the locale decide the order of day and month.
When parsing with a pattern, remember the smart resolver: add withResolverStyle(ResolverStyle.STRICT) and use uuuu, because strict mode cannot resolve yyyy without an era.
Example
When to use it
- Formatting order dates as 03/04/2026 in a CSV export that customers open in a spreadsheet.
- Printing 'Monday 28 December 2026' in a confirmation email using the customer's locale.
- Parsing dates from a legacy partner feed that sends values such as 05-MAR-2026.
Frequently asked questions
What is the difference between yyyy and YYYY in Java?
yyyy is the calendar year of era; YYYY is the week-based year, which differs for a few days around New Year. Use uuuu or yyyy for normal dates.
Is DateTimeFormatter thread-safe?
Yes. It is immutable and thread-safe, so keep formatters in static final constants. SimpleDateFormat was not, and needed one instance per thread.
Why do month names print in another language?
ofPattern(pattern) uses the JVM's default locale. Pass a locale explicitly, for example ofPattern("dd MMM uuuu", Locale.ENGLISH).
More examples
Pattern letter mix-ups side by side
Each wrong letter still produces plausible-looking output, which is why these bugs reach production. Only the first pattern is correct.
import java.time.*;
import java.time.format.*;
void main() {
LocalDateTime t = LocalDateTime.of(2026, 12, 31, 21, 7);
String[] patterns = {"uuuu-MM-dd HH:mm", "uuuu-mm-dd HH:mm", "uuuu-MM-dd hh:mm", "YYYY-MM-dd HH:mm", "uuuu-MM-DD"};
for (String p : patterns) {
IO.println(p + " -> " + t.format(DateTimeFormatter.ofPattern(p, Locale.US)));
}
}
// Output:
// uuuu-MM-dd HH:mm -> 2026-12-31 21:07
// uuuu-mm-dd HH:mm -> 2026-07-31 21:07
// uuuu-MM-dd hh:mm -> 2026-12-31 09:07
// YYYY-MM-dd HH:mm -> 2027-12-31 21:07
// uuuu-MM-DD -> 2026-12-365A shared formatter constant for a CSV export
A DateTimeFormatter is immutable, so one static constant can serve every request thread. Locale.ROOT keeps the output the same on any server.
import java.time.*;
import java.time.format.*;
static final DateTimeFormatter CSV_DATE = DateTimeFormatter.ofPattern("dd/MM/uuuu", Locale.ROOT);
record Order(String id, LocalDate placed, int pence) {}
void main() {
List<Order> orders = List.of(
new Order("A-100", LocalDate.of(2026, 4, 3), 1999),
new Order("A-101", LocalDate.of(2026, 4, 12), 4550));
IO.println("id,placed,total");
for (Order o : orders) {
IO.println(o.id() + "," + o.placed().format(CSV_DATE) + "," + "%d.%02d".formatted(o.pence() / 100, o.pence() % 100));
}
}
// Output:
// id,placed,total
// A-100,03/04/2026,19.99
// A-101,12/04/2026,45.50Strict and case-insensitive parsing
Strict resolving cannot turn year-of-era into a date without an era, so strict patterns need uuuu. DateTimeFormatterBuilder adds options such as case-insensitive month names that ofPattern alone cannot express.
import java.time.*;
import java.time.format.*;
void main() {
DateTimeFormatter yyyy = DateTimeFormatter.ofPattern("dd/MM/yyyy").withResolverStyle(ResolverStyle.STRICT);
DateTimeFormatter uuuu = DateTimeFormatter.ofPattern("dd/MM/uuuu").withResolverStyle(ResolverStyle.STRICT);
try {
LocalDate.parse("05/03/2026", yyyy);
} catch (DateTimeParseException e) {
IO.println("yyyy strict: failed");
}
IO.println("uuuu strict: " + LocalDate.parse("05/03/2026", uuuu));
DateTimeFormatter upper = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendPattern("dd-MMM-uuuu")
.toFormatter(Locale.ENGLISH);
IO.println("upper case: " + LocalDate.parse("05-MAR-2026", upper));
}
// Output:
// yyyy strict: failed
// uuuu strict: 2026-03-05
// upper case: 2026-03-05
Discussion