Java’s DateTimeFormatter
class offers a set of built-in, predefined formatters that make it easy to format and parse date-time values according to commonly accepted ISO standards. Using these constants ensures consistency, reduces errors, and improves readability in your applications—especially important in APIs, data exchange, and logging.
Some of the most frequently used predefined formatters in DateTimeFormatter
include:
ISO_LOCAL_DATE
: Formats or parses a date without time or offset, e.g., "2025-06-22"
.ISO_DATE_TIME
: Formats or parses a date and time with optional offset, e.g., "2025-06-22T14:30:00"
.BASIC_ISO_DATE
: A compact date format without delimiters, e.g., "20250622"
.import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class PredefinedFormatterDemo {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2025, 6, 22);
LocalDateTime dateTime = LocalDateTime.of(2025, 6, 22, 14, 30);
// ISO_LOCAL_DATE
String isoLocalDateStr = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
System.out.println("ISO_LOCAL_DATE: " + isoLocalDateStr);
// Output: ISO_LOCAL_DATE: 2025-06-22
// ISO_DATE_TIME
String isoDateTimeStr = dateTime.format(DateTimeFormatter.ISO_DATE_TIME);
System.out.println("ISO_DATE_TIME: " + isoDateTimeStr);
// Output: ISO_DATE_TIME: 2025-06-22T14:30:00
// BASIC_ISO_DATE
String basicIsoDateStr = date.format(DateTimeFormatter.BASIC_ISO_DATE);
System.out.println("BASIC_ISO_DATE: " + basicIsoDateStr);
// Output: BASIC_ISO_DATE: 20250622
}
}
Predefined formatters can also be used to convert strings back into date-time objects:
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class PredefinedParserDemo {
public static void main(String[] args) {
String dateStr = "2025-06-22";
String dateTimeStr = "2025-06-22T14:30:00";
String basicDateStr = "20250622";
LocalDate parsedDate = LocalDate.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE);
LocalDateTime parsedDateTime = LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ISO_DATE_TIME);
LocalDate parsedBasicDate = LocalDate.parse(basicDateStr, DateTimeFormatter.BASIC_ISO_DATE);
System.out.println("Parsed ISO_LOCAL_DATE: " + parsedDate);
System.out.println("Parsed ISO_DATE_TIME: " + parsedDateTime);
System.out.println("Parsed BASIC_ISO_DATE: " + parsedBasicDate);
}
}
Java’s predefined DateTimeFormatter
constants are your go-to tools for reliable and standardized date-time formatting and parsing. Whether you are working with pure dates, combined date-time values, or compact strings, these formatters simplify your code while promoting interoperability and correctness.
While Java’s predefined DateTimeFormatter
constants cover many standard use cases, often you’ll need custom date and time formats tailored to specific application needs—such as displaying dates in a localized style or matching legacy system requirements. Defining your own pattern strings with DateTimeFormatter.ofPattern()
gives you this flexibility.
Here are some common pattern symbols used in custom format strings:
Symbol | Description | Example Output |
---|---|---|
d |
Day of month (1-31) | 9 , 25 |
dd |
Day of month (2 digits) | 09 , 25 |
M |
Month number (1-12) | 3 , 12 |
MM |
Month number (2 digits) | 03 , 12 |
MMM |
Short month name | Mar , Dec |
MMMM |
Full month name | March , December |
y |
Year (variable length) | 2025 , 25 |
yyyy |
Year (4 digits) | 2025 |
H |
Hour (0-23) | 0 , 15 |
HH |
Hour (2 digits, 0-23) | 00 , 15 |
m |
Minute (0-59) | 5 , 45 |
mm |
Minute (2 digits) | 05 , 45 |
s |
Second (0-59) | 7 , 30 |
ss |
Second (2 digits) | 07 , 30 |
a |
AM/PM marker | AM , PM |
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class CustomFormatExample {
public static void main(String[] args) {
LocalDateTime dateTime = LocalDateTime.of(2025, 12, 31, 23, 59, 45);
// Define custom format pattern: "dd/MM/yyyy"
DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String formattedDate1 = dateTime.format(formatter1);
System.out.println("Formatted date (dd/MM/yyyy): " + formattedDate1);
// Output: Formatted date (dd/MM/yyyy): 31/12/2025
// Define custom format pattern: "yyyy-MM-dd HH:mm:ss"
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDate2 = dateTime.format(formatter2);
System.out.println("Formatted date-time (yyyy-MM-dd HH:mm:ss): " + formattedDate2);
// Output: Formatted date-time (yyyy-MM-dd HH:mm:ss): 2025-12-31 23:59:45
}
}
You can also parse strings that match your custom patterns back into date-time objects:
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class CustomParseExample {
public static void main(String[] args) {
String dateTimeStr = "31/12/2025 23:59:45";
// Pattern matching the input string
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
LocalDateTime parsedDateTime = LocalDateTime.parse(dateTimeStr, formatter);
System.out.println("Parsed LocalDateTime: " + parsedDateTime);
// Output: Parsed LocalDateTime: 2025-12-31T23:59:45
}
}
DateTimeParseException
.IllegalArgumentException
.MMM
, MMMM
) are case-sensitive in parsing.For example:
// This will throw DateTimeParseException because input does not match pattern
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDateTime.parse("31-12-2025", formatter);
Defining custom format patterns with DateTimeFormatter.ofPattern()
allows precise control over how dates and times appear and are read in your application. By understanding the symbols and matching your patterns exactly with your input strings, you can avoid exceptions and create clear, user-friendly date-time displays that fit your domain requirements perfectly.
When displaying dates and times to users around the world, it’s crucial to respect their local conventions—such as language, date order, month names, and separators. Java’s DateTimeFormatter
supports locale-specific formatting to ensure your application communicates dates and times in a familiar and culturally appropriate way.
Locale
with DateTimeFormatter
You can create a formatter that respects locale conventions by passing a Locale
instance to the formatter’s withLocale()
method or directly when building the formatter. This influences how textual elements like month names and day-of-week names appear, and sometimes the order or separator characters in formatted strings.
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class LocaleFormattingExample {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2025, 12, 31);
// Formatter with pattern and US locale
DateTimeFormatter usFormatter = DateTimeFormatter.ofPattern("dd MMMM yyyy").withLocale(Locale.US);
String usFormatted = date.format(usFormatter);
System.out.println("US format: " + usFormatted);
// Output: US format: 31 December 2025
// Formatter with French locale
DateTimeFormatter frFormatter = DateTimeFormatter.ofPattern("dd MMMM yyyy").withLocale(Locale.FRANCE);
String frFormatted = date.format(frFormatter);
System.out.println("French format: " + frFormatted);
// Output: French format: 31 décembre 2025
// Formatter with Japanese locale
DateTimeFormatter jpFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日").withLocale(Locale.JAPAN);
String jpFormatted = date.format(jpFormatter);
System.out.println("Japanese format: " + jpFormatted);
// Output: Japanese format: 2025年12月31日
}
}
Instead of custom patterns, Java also provides localized style formatters that automatically adapt the format based on locale:
DateTimeFormatter localizedFormatter = DateTimeFormatter.ofLocalizedDate(java.time.format.FormatStyle.LONG)
.withLocale(Locale.FRANCE);
System.out.println(date.format(localizedFormatter));
// Output: 31 décembre 2025
Locale-aware formatting improves user experience by showing dates in a familiar way—critical in global applications such as:
Ignoring locale can confuse users or lead to misinterpretation, especially with date order differences (e.g., 03/04/2025 means March 4th in US, but April 3rd in many other countries).
Using DateTimeFormatter
with Locale
empowers you to create date and time representations that feel natural and clear across different cultures and regions. Leveraging locale-sensitive formatting ensures your Java applications are globally friendly, reducing ambiguity and increasing user trust.
When working with date and time formatting in Java, understanding thread safety and how to reuse formatter instances is crucial—especially in multi-threaded or high-load environments. This section discusses the thread-safe design of DateTimeFormatter
introduced in Java 8, contrasting it with the legacy SimpleDateFormat
, and provides best practices for optimal performance and reliability.
DateTimeFormatter
vs. SimpleDateFormat
DateTimeFormatter
is immutable and thread-safe. Once created, it can be safely shared and used by multiple threads concurrently without synchronization. This means you can declare formatters as static constants or singletons and reuse them freely.
In contrast, the old java.text.SimpleDateFormat
is not thread-safe. Sharing a single instance across threads can lead to incorrect parsing or formatting results, race conditions, and unpredictable bugs. To use SimpleDateFormat
safely in multi-threaded contexts, you must either:
ThreadLocal<SimpleDateFormat>
), orDateTimeFormatter
Safelyimport java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class FormatterExample {
// Safe to share across threads
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static String formatCurrentDateTime() {
LocalDateTime now = LocalDateTime.now();
return now.format(FORMATTER);
}
public static void main(String[] args) {
// Multiple threads can safely call formatCurrentDateTime()
System.out.println(formatCurrentDateTime());
}
}
Here, FORMATTER
is a static final constant reused across any number of threads without issues.
SimpleDateFormat
import java.text.SimpleDateFormat;
import java.util.Date;
public class UnsafeSimpleDateFormat {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String formatDate(Date date) {
// This is NOT thread-safe!
return sdf.format(date);
}
}
If multiple threads invoke formatDate()
simultaneously, race conditions may corrupt the internal state of SimpleDateFormat
, leading to incorrect output.
DateTimeFormatter
’s thread safety, you can avoid costly synchronization or thread-local storage.DateTimeFormatter.ISO_LOCAL_DATE
to ensure consistency and reduce overhead.The immutable, thread-safe design of DateTimeFormatter
offers a robust and efficient way to format and parse date-time objects in multi-threaded environments. Unlike the legacy SimpleDateFormat
, it removes the need for complex synchronization or thread-local workarounds. Proper reuse of DateTimeFormatter
instances not only ensures correctness but also improves performance, making it a best practice in modern Java applications—especially under high load.