Why SimpleDateFormat Fails in Multithreaded Java and How to Fix It
This article explains why Java's SimpleDateFormat is not thread‑safe, demonstrates the problem with multithreaded examples, and presents two robust solutions—using ThreadLocal or the Java 8 immutable date‑time API—to ensure correct date formatting in concurrent environments.
Problem with SimpleDateFormat
Formatting and parsing dates with SimpleDateFormat looks easy, but the class is not thread‑safe, which leads to incorrect results when used from multiple threads.
Demo of the issue
Single‑thread test works as expected:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public final class DateUtils {
public static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
private DateUtils() {}
public static Date parse(String target) {
try {
return SIMPLE_DATE_FORMAT.parse(target);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
public static String format(Date target) {
return SIMPLE_DATE_FORMAT.format(target);
}
} private static void testSimpleDateFormatInSingleThread() {
final String source = "2019-01-11";
System.out.println(DateUtils.parse(source));
}Multithreaded test produces mixed results because the same formatter instance is shared:
private static void testSimpleDateFormatWithThreads() {
ExecutorService executorService = Executors.newFixedThreadPool(10);
final String source = "2019-01-11";
System.out.println(":: parsing date string ::");
IntStream.rangeClosed(0, 20)
.forEach(i -> executorService.submit(() -> System.out.println(DateUtils.parse(source))));
executorService.shutdown();
}Output shows duplicated and incorrect dates, demonstrating the thread‑safety problem.
Official guidance
Date formats are not synchronized. It is recommended to create a separate format instance for each thread. If multiple threads access a format, it must be externally synchronized.
Solution 1 – ThreadLocal
Store a separate SimpleDateFormat per thread using ThreadLocal:
public final class DateUtilsThreadLocal {
public static final ThreadLocal<SimpleDateFormat> SIMPLE_DATE_FORMAT =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
private DateUtilsThreadLocal() {}
public static Date parse(String target) {
try {
return SIMPLE_DATE_FORMAT.get().parse(target);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
public static String format(Date target) {
return SIMPLE_DATE_FORMAT.get().format(target);
}
}Solution 2 – Java 8 Date‑Time API
Use the immutable, thread‑safe classes from java.time instead of SimpleDateFormat:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class DateUtilsJava8 {
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private DateUtilsJava8() {}
public static LocalDate parse(String target) {
return LocalDate.parse(target, DATE_TIME_FORMATTER);
}
public static String format(LocalDate target) {
return target.format(DATE_TIME_FORMATTER);
}
}Conclusion
Java 8’s immutable date‑time classes are inherently thread‑safe, making them the preferred choice. If you must keep using SimpleDateFormat, wrap it in a ThreadLocal or synchronize access.
Happy coding!
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
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.
