Thread Safety Issues of SimpleDateFormat.parse() and format() Methods and Their Solutions
This article explains why SimpleDateFormat.parse() and SimpleDateFormat.format() are not thread‑safe in Java, analyzes the underlying causes involving shared Calendar objects, and presents three practical solutions—including per‑thread instances, synchronized blocks, and ThreadLocal usage—to eliminate concurrency bugs.
1. Thread Safety Problems of SimpleDateFormat.parse()
1.1 Incorrect Example
The following code uses a static SimpleDateFormat instance in multiple threads, which can cause NumberFormatException due to race conditions:
import java.text.SimpleDateFormat;
public class SimpleDateFormatTest {
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
for (int i = 0; i < 20; ++i) {
Thread thread = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "--" +
SIMPLE_DATE_FORMAT.parse("2020-06-01 11:35:00"));
} catch (Exception e) {
e.printStackTrace();
}
}, "Thread-" + i);
thread.start();
}
}
}The program throws errors because SimpleDateFormat shares mutable state across threads.
1.2 Cause Analysis
SimpleDateFormat extends DateFormat and relies on a shared Calendar object for both parse and format . The internal Calendar.clear() and other modifications are not synchronized, so when one thread clears or updates the calendar, another thread may read inconsistent data, leading to thread‑safety violations.
1.3 Solutions
Method 1: Create a new SimpleDateFormat per thread
import java.text.SimpleDateFormat;
public class SimpleDateFormatTest {
public static void main(String[] args) {
for (int i = 0; i < 20; ++i) {
Thread thread = new Thread(() -> {
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(Thread.currentThread().getName() + "--" +
sdf.parse("2020-06-01 11:35:00"));
} catch (Exception e) {
e.printStackTrace();
}
}, "Thread-" + i);
thread.start();
}
}
}Method 2: Synchronize access to a shared instance
public class SimpleDateFormatTest {
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
for (int i = 0; i < 20; ++i) {
Thread thread = new Thread(() -> {
try {
synchronized (SIMPLE_DATE_FORMAT) {
System.out.println(Thread.currentThread().getName() + "--" +
SIMPLE_DATE_FORMAT.parse("2020-06-01 11:35:00"));
}
} catch (Exception e) {
e.printStackTrace();
}
}, "Thread-" + i);
thread.start();
}
}
}Method 3: Use ThreadLocal to hold a separate SimpleDateFormat for each thread
import java.text.SimpleDateFormat;
public class SimpleDateFormatTest {
private static final ThreadLocal
SAFE_SDF =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static void main(String[] args) {
for (int i = 0; i < 20; ++i) {
Thread thread = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "--" +
SAFE_SDF.get().parse("2020-06-01 11:35:00"));
} catch (Exception e) {
e.printStackTrace();
}
}, "Thread-" + i);
thread.start();
}
}
}2. Thread Safety Problems of SimpleDateFormat.format()
2.1 Incorrect Example
The following code formats dates using a shared static SimpleDateFormat inside a thread pool, which leads to duplicated or incorrect output:
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;
public class SimpleDateFormatTest {
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000));
for (int i = 0; i < 1000; i++) {
int finalI = i;
threadPool.execute(() -> {
Date date = new Date(finalI * 1000);
formatAndPrint(date);
});
}
threadPool.shutdown();
}
private static void formatAndPrint(Date date) {
String result = simpleDateFormat.format(date);
System.out.println("时间:" + result);
}
}2.2 Cause Analysis
During formatting, SimpleDateFormat.format() calls calendar.setTime(date) . Because the underlying Calendar is shared, one thread may modify it while another thread is still using it, causing inconsistent results and duplicate timestamps.
2.3 Solutions
The same three approaches from section 1 can be applied: per‑thread instances, synchronized blocks, or ThreadLocal . Each eliminates the shared mutable state and guarantees correct formatting in a concurrent environment.
By adopting any of these patterns, developers can safely use date parsing and formatting utilities in multithreaded Java applications.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.