Java LocalDate Format: Patterns and Examples
Learn how to format a Java LocalDate with DateTimeFormatter, including predefined and custom patterns, locale handling, parsing, and common pitfalls.
When you need to convert a LocalDate to a string in Java, the DateTimeFormatter class is the standard tool. It provides predefined formats, custom patterns, and locale-specific output. This article explains how to format a LocalDate, parse formatted strings back into dates, and avoid common pitfalls.
Using Predefined DateTimeFormatter Patterns
The java.time.format.DateTimeFormatter class offers several predefined constants for common ISO-based formats. For a LocalDate, the most directly useful one is ISO_LOCAL_DATE, which produces output like 2025-04-01. The related ISO_DATE formatter can include an offset when used with date-time types that carry one, but with a LocalDate it produces the same local date representation.
import java.time.LocalDate; import java.time.format.DateTimeFormatter; LocalDate date = LocalDate.of(2025, 4, 1); String iso = date.format(DateTimeFormatter.ISO_LOCAL_DATE); System.out.println(iso); // 2025-04-01
These constants are immutable and thread-safe, so you can reuse them across your application. They are a good default when you need a standard, machine-readable representation.
Creating Custom Patterns with ofPattern()
When the predefined formats don't match your requirement, use DateTimeFormatter.ofPattern() with a pattern string. The pattern uses letters to represent date fields, such as y for year, M for month, d for day, and E for day-of-week. For example, this formatter produces output like 01-Apr-2025 in an English locale:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MMM-yyyy"); String formatted = date.format(formatter); System.out.println(formatted); // 01-Apr-2025
The pattern letters are case-sensitive: M means month and m means minute, while d means day-of-month and D means day-of-year. Mixing these up is a common source of bugs. For a full list of pattern letters, refer to the DateTimeFormatter Javadoc.
Formatting with Locale
Month names and weekday names depend on the locale. By default, ofPattern uses the JVM's default locale, which may not be what you want in a multi-region application. Pass a Locale as the second argument to ofPattern to control the language and cultural conventions.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.FRANCE); String formatted = date.format(formatter); System.out.println(formatted); // 01 avril 2025
Using the same pattern with a different locale produces the appropriate month name. This is essential for user-facing output that must respect the user's language.
Parsing LocalDate from Strings
Formatting and parsing are two sides of the same coin. To convert a string back to a LocalDate, use LocalDate.parse() with a DateTimeFormatter. If the string does not match the pattern or represents an invalid date, a DateTimeParseException is thrown.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy"); LocalDate parsed = LocalDate.parse("01-04-2025", formatter); System.out.println(parsed); // 2025-04-01
Be careful with patterns that use y (year-of-era) versus u (year). For most use cases, u is safer because it handles years before 1 CE without ambiguity. For example, ofPattern("uuuu-MM-dd") is a common choice for ISO-like output.
Common Mistakes and Edge Cases
One frequent error is confusing y (year-of-era) with Y (week-based-year). The week-based-year is used with week-of-year patterns and can produce unexpected results around New Year's Day. For ordinary date formatting, stick with y or u.
Another issue is using D (day-of-year) when you mean d (day-of-month). D returns a number from 1 to 365, or 366 in a leap year, not the day within the month. For example, ofPattern("DD/MM/yyyy") would interpret 05/04/2025 as the 5th day of the year, which is January 5, not April 5.
Also, the pattern string is parsed when the formatter is created. If you call DateTimeFormatter.ofPattern() repeatedly with the same pattern, that work is repeated. Reuse formatter instances for fixed patterns; the next section shows a common pattern.
Reusing Formatters for Performance and Thread Safety
DateTimeFormatter instances are immutable and thread-safe, so you can safely share them across threads. Predefined constants like ISO_LOCAL_DATE are already static. For custom patterns, define them as static final fields to avoid parsing the pattern string again for each call.
import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.Locale; public class DateUtils { private static final DateTimeFormatter CUSTOM_FORMATTER = DateTimeFormatter.ofPattern("dd-MMM-yyyy", Locale.ENGLISH); public static String format(LocalDate date) { return date.format(CUSTOM_FORMATTER); } }
This approach avoids the overhead of creating a new formatter on each call and ensures consistent behavior across the application. If you need different locales, create one formatter instance per locale and reuse those instances.
For high-throughput systems, formatting a LocalDate is generally cheap, but reusing formatters is still a good practice because it avoids repeated pattern parsing.