Why SimpleDateFormat Breaks in Multithreaded Java and How to Fix It

This article explains why the classic Java SimpleDateFormat class is not thread‑safe, demonstrates the problems caused by using a static instance in concurrent code, and presents four practical solutions—including creating new instances, synchronizing, using ThreadLocal, and switching to the immutable DateTimeFormatter.

Programmer DD
Programmer DD
Programmer DD
Why SimpleDateFormat Breaks in Multithreaded Java and How to Fix It

Introduction

In everyday development we often use time‑related classes, and SimpleDateFormat is familiar for formatting and parsing dates. However, SimpleDateFormat is not thread‑safe, which leads to exceptions in multithreaded environments.

Problem Scenario Reproduction

Typical code defines SimpleDateFormat as a static variable to avoid frequent object creation:

public class SimpleDateFormatTest {
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static String formatDate(Date date) throws ParseException {
        return sdf.format(date);
    }
    public static Date parse(String strDate) throws ParseException {
        return sdf.parse(strDate);
    }
    public static void main(String[] args) throws InterruptedException, ParseException {
        System.out.println(sdf.format(new Date()));
    }
}

Running this code in a single thread works fine, but under multiple threads it produces incorrect results and sometimes throws NumberFormatException or crashes.

Why SimpleDateFormat Is Not Thread‑Safe

Because the static SimpleDateFormat instance is shared, its internal Calendar field is also shared. One thread may set the calendar time while another thread reads it, causing date mismatches and exceptions. The source code of format() shows it operates on a shared Calendar object.

private StringBuffer format(Date date, StringBuffer toAppendTo) {
    calendar.setTime(date);
    // ... further processing ...
    return toAppendTo;
}

The Alibaba Java Development Manual explicitly warns that date formats are not synchronized and recommends creating separate format instances for each thread or synchronizing external access.

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

Solutions

Create a New Instance When Needed

public static String formatDate(Date date) throws ParseException {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.format(date);
}

public static Date parse(String strDate) throws ParseException {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.parse(strDate);
}

This eliminates shared state but incurs object‑creation overhead.

Synchronize Access

private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

public static String formatDate(Date date) throws ParseException {
    synchronized (sdf) {
        return sdf.format(date);
    }
}

public static Date parse(String strDate) throws ParseException {
    synchronized (sdf) {
        return sdf.parse(strDate);
    }
}

Synchronizing guarantees correctness but may become a bottleneck under high concurrency.

ThreadLocal

private static final ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
    @Override
    protected DateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }
};

public static Date parse(String dateStr) throws ParseException {
    return threadLocal.get().parse(dateStr);
}

public static String format(Date date) {
    return threadLocal.get().format(date);
}

Each thread gets its own SimpleDateFormat instance, removing contention.

Use Java 8 DateTimeFormatter

private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

public static String formatDate2(LocalDateTime date) {
    return formatter.format(date);
}

public static LocalDateTime parse2(String dateNow) {
    return LocalDateTime.parse(dateNow, formatter);
}

DateTimeFormatter is immutable and thread‑safe, providing the most efficient solution.

All four approaches eliminate the thread‑safety issue demonstrated earlier.

Alibaba Java Development Manual
Alibaba Java Development Manual
Console output showing errors
Console output showing errors
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javathread safetySimpleDateFormatThreadLocalDateTimeFormatter
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.