Index

Formatting and Parsing with `DateTimeFormatter`

Java Date and Time

8.1 Using Predefined Formatters

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.

Common Predefined Formatters

Some of the most frequently used predefined formatters in DateTimeFormatter include:

Formatting Examples

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
    }
}

Parsing Examples

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);
    }
}

Why Use Predefined Formatters?

Summary

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.

Index

8.2 Defining Custom Format Patterns

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.

Understanding Format Pattern Symbols

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

Formatting Examples

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
    }
}

Parsing Examples

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
    }
}

Common Pitfalls and Exceptions

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);

Summary

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.

Index

8.3 Locale-Specific Formatting

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.

Using 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.

Examples: Formatting Dates in Different Locales

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日
    }
}

How Locale Affects Formatting

Using Predefined Localized Formatters

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

Applications in UI and Reporting

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).

Summary

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.

Index

8.4 Thread Safety and Reusability

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.

Thread Safety: 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:

Example: Reusing DateTimeFormatter Safely

import 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.

Contrast with 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.

Performance and Best Practices

Summary

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.

Index