Java's SimpleDateFormat is not thread-safe, Use carefully in multi-threaded environments
JavaJune 22, 20181 mins readSimpleDateFormat is used to format and parse dates in Java.
You can create an instance of SimpleDateFormat
with a date-time pattern like yyyy-MM-dd HH:mm:ss
, and then use that instance to format and parse dates to/from string.
One of the most important things to note about SimpleDateFormat
class is that it is not thread-safe and causes issues in multi-threaded environments if not used properly.
I’m writing this post because I’ve seen developers using SimpleDateFormat
blindly in multi-threaded environments without knowing and dealing with the fact that it is not thread-safe.
SimpleDateFormat thread safety issue Example
Let’s understand what happens when we try to use SimpleDateFormat
in a multi-threaded environment without any synchronization.
Following is an example of a very simple class where we parse a given date with a predefined pattern, but we do it concurrently from multiple threads.
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleDateFormatThreadUnsafetyExample {
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
public static void main(String[] args) {
String dateStr = "2018-06-22T10:00:00";
ExecutorService executorService = Executors.newFixedThreadPool(10);
Runnable task = new Runnable() {
@Override
public void run() {
parseDate(dateStr);
}
};
for(int i = 0; i < 100; i++) {
executorService.submit(task);
}
executorService.shutdown();
}
private static void parseDate(String dateStr) {
try {
Date date = simpleDateFormat.parse(dateStr);
System.out.println("Successfully Parsed Date " + date);
} catch (ParseException e) {
System.out.println("ParseError " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Simple enough! We have a single instance of SimpleDateFormat
that we use to parse dates from multiple threads.
If you run the above program, it will produce an output like this (your output might be different than mine, but it will have similar errors) -
# Output
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Thu Jun 22 10:00:00 IST 2220
java.lang.NumberFormatException: multiple points
at java.base/jdk.internal.math.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
at java.base/jdk.internal.math.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.base/java.lang.Double.parseDouble(Double.java:543)
at java.base/java.text.DigitList.getDouble(DigitList.java:169)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2098)
at java.base/java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1915)
at java.base/java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1529)
at java.base/java.text.DateFormat.parse(DateFormat.java:386)
at SimpleDateFormatThreadUnsafetyExample.parseDate(SimpleDateFormatThreadUnsafetyExample.java:32)
at SimpleDateFormatThreadUnsafetyExample.access$000(SimpleDateFormatThreadUnsafetyExample.java:8)
at SimpleDateFormatThreadUnsafetyExample$1.run(SimpleDateFormatThreadUnsafetyExample.java:19)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:514)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.base/java.lang.Thread.run(Thread.java:844)
java.lang.NumberFormatException: For input string: ""
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.base/java.lang.Long.parseLong(Long.java:702)
at java.base/java.lang.Long.parseLong(Long.java:817)
at java.base/java.text.DigitList.getLong(DigitList.java:195)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2093)
at java.base/java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2222)
at java.base/java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1529)
at java.base/java.text.DateFormat.parse(DateFormat.java:386)
at SimpleDateFormatThreadUnsafetyExample.parseDate(SimpleDateFormatThreadUnsafetyExample.java:32)
at SimpleDateFormatThreadUnsafetyExample.access$000(SimpleDateFormatThreadUnsafetyExample.java:8)
at SimpleDateFormatThreadUnsafetyExample$1.run(SimpleDateFormatThreadUnsafetyExample.java:19)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:514)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.base/java.lang.Thread.run(Thread.java:844)
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
## More output....... (Omitted for brevity)
The SimpleDateFormat
class mutates its internal state for formatting and parsing dates. That’s why it results in these issues when multiple threads use the same instance of SimpleDateFormat
concurrently.
How should I use SimpleDateFormat in a multi-threaded environment?
You have two options -
Create a new instance of
SimpleDateFormat
for each thread.Synchronize concurrent access by multiple threads with a
synchronized
keyword or alock
.
SimpleDateFormat
for every thread
1. Create separate instances of import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleDateFormatThreadUnsafetyExample {
public static void main(String[] args) {
String dateStr = "2018-06-22T10:00:00";
ExecutorService executorService = Executors.newFixedThreadPool(10);
Runnable task = new Runnable() {
@Override
public void run() {
parseDate(dateStr);
}
};
for(int i = 0; i < 100; i++) {
executorService.submit(task);
}
executorService.shutdown();
}
private static void parseDate(String dateStr) {
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
Date date = simpleDateFormat.parse(dateStr);
System.out.println("Successfully Parsed Date " + date);
} catch (ParseException e) {
System.out.println("ParseError " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}
Notice that, I’ve moved the SimpleDateFormat
instance creation inside parseDate()
method. This way, we’re creating a new instance for every thread. The output of the above program won’t have any errors -
# Output
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
Successfully Parsed Date Fri Jun 22 10:00:00 IST 2018
## More output....... (Omitted for brevity)
SimpleDateFormat
but synchronize concurrent access
2. Share the same instance of
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SimpleDateFormatThreadUnsafetyExample {
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
public static void main(String[] args) {
String dateStr = "2018-06-22T10:00:00";
ExecutorService executorService = Executors.newFixedThreadPool(10);
Runnable task = new Runnable() {
@Override
public void run() {
parseDate(dateStr);
}
};
for(int i = 0; i < 100; i++) {
executorService.submit(task);
}
executorService.shutdown();
}
private synchronized static void parseDate(String dateStr) {
try {
Date date = simpleDateFormat.parse(dateStr);
System.out.println("Successfully Parsed Date " + date);
} catch (ParseException e) {
System.out.println("ParseError " + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}
In the above example, I’ve added a synchronized
keyword to the parseDate()
method. In this case, only one thread can enter the parseDate()
method at a time.
My Advice - Don’t use SimpleDateFormat.
Yes! Don’t use SimpleDateFormat
. Java 8 has a better and more enhanced DateTimeFormatter which is also thread-safe.
You should also avoid using Date
and Calendar
classes, and try to use Java 8 DateTime classes like OffsetDateTime
, ZonedDateTime
, LocalDateTime
, LocalDate
, LocalTime
etc. They have way more capabilities than the Date
and Calendar
classes.
Until next time…