To format a LocalDate in Java, we call date.format(formatter) with a DateTimeFormatter, or date.toString() when the default format is enough. The default format pattern is yyyy-MM-dd. A LocalDate holds only a year, a month and a day, so the formatter can print those three fields in any order and with any separators we like.
We format a LocalDate to a String when we show a date in a report or on a screen, and when we write it into a JSON field or a file name such as invoice-20261005.pdf. Each of these needs a different layout, and the Java LocalDate format API covers all of them with one method.
The following example formats the same date in the three ways we use most often.
LocalDate date = LocalDate.of(2026, 10, 5);
String iso = date.toString(); // 2026-10-05
DateTimeFormatter dmy = DateTimeFormatter.ofPattern("dd-MM-yyyy");
String custom = date.format(dmy); // 05-10-2026
DateTimeFormatter longStyle = DateTimeFormatter
.ofLocalizedDate(FormatStyle.LONG).withLocale(Locale.US);
String localized = date.format(longStyle); // October 5, 2026
Notice that the LocalDate itself never changes. The formatter only decides how its three fields are printed. In the rest of the post we go through the ISO constants, custom patterns, localized styles and the way back from a String with parse().
1. Choosing a LocalDate Format
A LocalDate has no format of its own. The DateTimeFormatter we pass to format() decides the output, so the first question is who reads the String. A String that another program reads should use the ISO format, because every language can parse it. A String that a person reads should match the layout the person expects, which depends on the country.

| Who reads the String | Formatter | Code | Result for 2026-10-05 |
|---|---|---|---|
| Another program, a database, JSON | toString() or ISO_LOCAL_DATE | date.toString() | 2026-10-05 |
| A report or file name with a fixed layout | ofPattern() | date.format(DateTimeFormatter.ofPattern(“yyyyMMdd”)) | 20261005 |
| A user, in the layout of their country | ofLocalizedDate() | date.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.UK)) | 5 Oct 2026 |
We can call format() on either object. The calls date.format(formatter) and formatter.format(date) give the same String. The second form reads better when we keep one formatter as a constant and format many dates with it.
2. The Default Format and the ISO Constants
The toString() method prints the date as yyyy-MM-dd, with a four-digit year and a two-digit month and day. The constant DateTimeFormatter.ISO_LOCAL_DATE prints the same String. So does ISO_DATE, because it adds a zone offset only when the value has one, and a LocalDate has none. We use these three forms for logs and APIs, because the String sorts in date order and parses back without a pattern.
The DateTimeFormatter class has a few more predefined constants that work on a plain date.
LocalDate date = LocalDate.of(2026, 10, 5);
String isoLocal = date.format(DateTimeFormatter.ISO_LOCAL_DATE); // 2026-10-05
String isoDate = date.format(DateTimeFormatter.ISO_DATE); // 2026-10-05
String basic = date.format(DateTimeFormatter.BASIC_ISO_DATE); // 20261005
String ordinal = date.format(DateTimeFormatter.ISO_ORDINAL_DATE); // 2026-278
String week = date.format(DateTimeFormatter.ISO_WEEK_DATE); // 2026-W41-1
The BASIC_ISO_DATE constant is a good choice for file names because it has no separators. The ordinal form prints the day of the year, and the week form prints the week number and the day of the week. Both are rare in application code, but they appear in some data feeds.
3. Custom Patterns With ofPattern()
If we have to format the LocalDate in a date pattern that is not available inbuilt, we define our own pattern with DateTimeFormatter.ofPattern() and pass the formatter to LocalDate.format(). In a pattern, each letter stands for a date field, and every other character such as – or / is copied into the output as it is. Text that contains letters, such as the word Week, goes between single quotes, so the formatter does not read it as pattern letters.
LocalDate date = LocalDate.of(2026, 10, 5);
String slash = date.format(DateTimeFormatter.ofPattern("MM/dd/yyyy")); // 10/05/2026
String monthName = date.format(DateTimeFormatter.ofPattern("dd MMM yyyy")); // 05 Oct 2026
String full = date.format(DateTimeFormatter.ofPattern("EEEE, MMMM d, yyyy")); // Monday, October 5, 2026
String noSeparator = date.format(DateTimeFormatter.ofPattern("yyyyMMdd")); // 20261005
String literal = date.format(DateTimeFormatter.ofPattern("'Week' w 'of' yyyy")); // Week 41 of 2026
The number of letters controls the width of a field.
- One letter prints the shortest form, so d prints 5.
- Two letters pad a number with a zero, so dd prints 05.
- Three letters print the short name and four letters the full name, so MMM prints Oct and MMMM prints October.
| Letters | Field | 2026-10-05 prints as |
|---|---|---|
| yyyy / yy | Year of era | 2026 / 26 |
| M / MM | Month number | 10 / 10 (for March, 3 / 03) |
| MMM / MMMM | Month name | Oct / October |
| d / dd | Day of month | 5 / 05 |
| EEE / EEEE | Day of week | Mon / Monday |
| D | Day of year | 278 |
| w | Week of year | 41 |
| ‘text’ | Literal text | text |
The month and weekday names come from the default locale of the JVM, so the output changes when the app runs on a server with another language. We pass the Locale as the second argument of ofPattern() whenever a pattern prints a name. The DateTimeFormatter post lists every pattern letter with its meaning.
String withLocale = date.format(
DateTimeFormatter.ofPattern("EEEE, d MMMM yyyy", Locale.GERMANY)); // Montag, 5 Oktober 2026
4. Localized Dates With FormatStyle and Locale
When the String is shown to a user, we do not want to write one pattern per country. Instead, we call DateTimeFormatter.ofLocalizedDate() with a FormatStyle, and the JDK picks the pattern for the country from the locale data it ships with. The FormatStyle enum has four constants, FULL, LONG, MEDIUM and SHORT. Each one stands for a length of the output, not for a fixed pattern. Based on the Locale, each constant may output a different string.
LocalDate date = LocalDate.of(2026, 10, 5);
DateTimeFormatter us = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(Locale.US);
String fullUs = date.format(us); // Monday, October 5, 2026
DateTimeFormatter fr = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(Locale.FRANCE);
String shortFr = date.format(fr); // 05/10/2026
The four styles differ between locales in the order of the fields, the month name and the separators. Note that SHORT in the US puts the month first and prints a two-digit year. A SHORT String is fine on a screen, but we never store it, because 10/5/26 means different dates in different countries.
| Style | Locale.US | Locale.UK | Locale.FRANCE |
|---|---|---|---|
| FULL | Monday, October 5, 2026 | Monday, 5 October 2026 | lundi 5 octobre 2026 |
| LONG | October 5, 2026 | 5 October 2026 | 5 octobre 2026 |
| MEDIUM | Oct 5, 2026 | 5 Oct 2026 | 5 oct. 2026 |
| SHORT | 10/5/26 | 05/10/2026 | 05/10/2026 |
Without withLocale(), the formatter uses the default locale of the JVM, which comes from the machine that runs the code. In a web app, the right locale is the one of the user, which we read from the request, as shown in localized date formats.
5. Formatting LocalDateTime and ZonedDateTime
The format() call is the same for LocalDateTime and ZonedDateTime. The difference is which pattern letters the value can fill. A LocalDateTime also has a time, so the pattern can use HH for the hour, mm for the minute and ss for the second. A ZonedDateTime also has a zone, so the pattern can use z for the zone name and XXX for the offset.
LocalDateTime dateTime = LocalDateTime.of(2026, 10, 5, 14, 30, 0);
String dt = dateTime.format(DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm")); // 05-10-2026 14:30
String dtDefault = dateTime.toString(); // 2026-10-05T14:30
ZonedDateTime zoned = dateTime.atZone(ZoneId.of("Asia/Kolkata"));
String zdt = zoned.format(DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm z")); // 05-10-2026 14:30 IST
String zdtOffset = zoned.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mmXXX")); // 2026-10-05T14:30+05:30
A pattern with time letters does not work on a LocalDate, because the date has no hour to print. The call throws an UnsupportedTemporalTypeException. To fix it, we either drop the time letters from the pattern or convert the date to a LocalDateTime with atStartOfDay() first, which sets the time to 00:00.
String bad = date.format(DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm")); // UnsupportedTemporalTypeException: Unsupported field: HourOfDay
String fixed = date.atStartOfDay().format(DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm")); // 05-10-2026 00:00
6. Converting the String Back With parse()
Every formatter works in both directions. The static method LocalDate.parse(text) reads the ISO format, and LocalDate.parse(text, formatter) reads the custom pattern we formatted with. When the text does not match the pattern, parse() throws a DateTimeParseException. Input that comes from a user or a file can always be wrong, so we parse it in a small method that catches the exception and returns an empty Optional.
DateTimeFormatter dmy = DateTimeFormatter.ofPattern("dd-MM-yyyy");
LocalDate parsedIso = LocalDate.parse("2026-10-05"); // 2026-10-05
LocalDate parsedCustom = LocalDate.parse("05-10-2026", dmy); // 2026-10-05
Optional<LocalDate> safe = parseSafely("2026/10/05", dmy); // Optional.empty
static Optional<LocalDate> parseSafely(String text, DateTimeFormatter formatter) {
try {
return Optional.of(LocalDate.parse(text, formatter));
} catch (DateTimeParseException e) {
return Optional.empty();
}
}
The post on converting a String to LocalDate covers the strict, smart and lenient modes of the parser.
7. LocalDate Format FAQs
7.1. Why Does the Pattern YYYY Print the Wrong Year?
Because the capital Y is the week-based year, not the calendar year. The week-based year changes on the first day of the first week of a year, which can be a few days before January 1. For December 31, 2025, the pattern dd-MM-YYYY prints 2026. The bug shows up only in the last days of December, so a test that runs in October never catches it. Two more letters cause the same kind of bug. DD is the day of the year, not the day of the month, and mm is the minute, not the month.
LocalDate newYearsEve = LocalDate.of(2025, 12, 31);
String right = newYearsEve.format(DateTimeFormatter.ofPattern("dd-MM-yyyy")); // 31-12-2025
String wrong = newYearsEve.format(
DateTimeFormatter.ofPattern("dd-MM-YYYY", Locale.US)); // 31-12-2026
7.2. How Do We Format a java.util.Date With DateTimeFormatter?
We convert the Date to a LocalDate first and format the LocalDate. A java.util.Date is a point in time without a zone, so the same Date can fall on different calendar days in different parts of the world. The conversion needs a ZoneId to pick the day. The old SimpleDateFormat still works, but it is not thread-safe, so a shared DateTimeFormatter is the safer choice in new code.
Date legacyDate = new Date();
LocalDate fromLegacy = legacyDate.toInstant()
.atZone(ZoneId.systemDefault()).toLocalDate();
String legacyFormatted = fromLegacy.format(DateTimeFormatter.ofPattern("dd-MM-yyyy")); // today, e.g. 05-10-2026
7.3. Is DateTimeFormatter Thread-Safe?
Yes. A DateTimeFormatter is immutable, so we create it once as a static final constant and share it between threads and requests. Creating a new formatter inside every call works too, but it reads the pattern String again each time.
8. Conclusion
A LocalDate turns into a String with toString() for the ISO format yyyy-MM-dd or with ofPattern() for a layout we choose. For text that a user reads, ofLocalizedDate() plus a Locale builds the right layout for the country. The same format() call works on LocalDateTime and ZonedDateTime, which only add time and zone letters to the pattern.
Two habits avoid most date bugs. We write the year as lowercase yyyy, never YYYY, and we pass a Locale whenever the output contains a month or weekday name.
9. References
Happy Learning !!
Please doublecheck the custom pattern.. you wan’t to have dd/MM/YYYY but the comment say dd-MM-YYYY
Thanks buddy for noticing and reporting it. Much appreciated!
2.2 doesn’t work : java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: MinuteOfHour
Use MM, MMM for month, mm is for minute : )