Updated at 2019-09-01:

Starting from JDK 8, the issue described in this post is no longer a problem thanks to the new Date Time API. Basically, this API is loosely based on the popular Java library JodaTime. It has advantages of Joda Time, and is very clear, concise and yet very flexible. Another advantage is that all time representations in Java 8 Date Time API are immutable and thus thread-safe. Of course, the old problematic SimpleDateFormat is replaced by more elegant tools.

Let’s take a look at the usage before and after:

1
2
3
4
5
// Old
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date now = new Date();
String formattedDate = dateFormat.format(now);
Date parsedDate = dateFormat.parse(formattedDate);
1
2
3
4
5
// New
LocalDate now = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String formattedDate = now.format(formatter);
LocalDate parsedDate = LocalDate.parse(formattedDate, formatter);

Original Post:

SimpleDateFormat from JDK is not thread-safe, so be careful when it is used in a concurrent environment. Although it is clearly stated in the JavaDoc of the API that it’s not synchronized, I think many would not notice it until we encounter problems caused by the issue.

Let’s see an example first.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package gan.blog.example.sdf;

import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * creates and starts 10 threads, each of which parses a same time stamp using
 * shared SimpleDateFormat
 */
public class SimpleDateFormatTester {

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

  public static void main(String[] args) {

    for (int i = 0; i < 5; i++) {
      Thread t = new Thread() {
        public void run() {
          try {
            System.out.println(sdf.parse("2014-06-27 10:33:09.258 +0800"));
          } catch (ParseException e) {
            e.printStackTrace();
          }
        }
      };

      t.start();
    }
  }
}

Above program starts 5 threads, each of which parses a date string using shared SimpleDateFormat. Run above program several times, very likely you will get one of the below unexpected results or exceptions.

  1. Exception in thread “Thread-4” java.lang.NumberFormatException: For input string: “”
  2. Exception in thread “Thread-1” java.lang.NumberFormatException: multiple points
  3. Thu Jun 27 10:33:09 CST 2024
  4. Exception in thread “Thread-2” java.lang.NumberFormatException: empty String
  5. Fri Jun 27 10:33:09 CST 2200
  6. ……

As shown above, sometimes you get an exception (mostly the NumberFormatException: For input string: ""), sometimes it just gives incorrect values.

To avoid the problem, you can create a SimpleDateFormat each time when you parse a string. Of course, this is not efficient enough. If you want a more superior solution, you can use ThreadLocal to make sure each thread has its own copy, you probably also want to cache the created instances. To see the details of this solution, you can refer to up Jesper’s blog post, it also provides a performance comparison of several solutions.