Back To The Future With SimpleDateFormat
Some time ago I had to fix a really strange bug in a Java web application. A date displayed to the user was going crazy. Where it should be 20 November 2011 it was something like 12 January 510 or 4 October 3040. The app was obviously broken...
I checked the code and at first it looked all right. There was an utility class looking something like that:
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TimeUtil {
private static final DateFormat FORMAT =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String formatDate(Date date) {
return FORMAT.format(date);
}
public static Date parse(String dateString) throws ParseException {
return FORMAT.parse(dateString);
}
}
I debugged the code and it run OK. I googled a few times and nobody seemed to had this problem. So I went back and forth between source files wondering how this was possible... and then I got an idea. I wrote a small program to prove me right and it all became clear. The program looked like that:
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
class DateFormatBugThread extends Thread {
private DateFormat df;
public DateFormatBugThread(DateFormat df) {
this.df = df;
}
public void run() {
for(int i=0; i<10; i++) {
try {
Date d = new Date(df.parse("2011-08-25 22:05").getTime());
System.out.println(df.format(d));
} catch (Exception ex) {
System.out.println(ex);
}
}
}
}
public class Main {
public static void main(String[] args) throws Exception {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
DateFormatBugThread t1 = new DateFormatBugThread(df);
DateFormatBugThread t2 = new DateFormatBugThread(df);
t1.start();
t2.start();
t1.join();
t2.join();
}
}
And the results were:
java.lang.NumberFormatException: For input string: ""
2011-08-25 22:05
2200-08-25 22:05
2200-08-25 22:05
1970-01-05 22:20
1970-01-05 22:20
1970-08-01 22:05
1970-08-01 22:05
java.lang.NumberFormatException: For input string: ""
java.lang.NumberFormatException: For input string: ""
2011-08-01 22:05
2011-08-01 22:05
java.lang.NumberFormatException: For input string: ""
1970-01-01 22:05
java.lang.NumberFormatException: For input string: ""
2011-08-25 22:05
0005-08-25 22:05
2011-08-25 22:05
2011-08-25 22:05
2011-08-25 22:05
As you can see the SimpleDateFormat class is not thread-safe. There is even a caution in the Oracle JavaDoc.
Solution
The simplest solution is to create a new SimpleDateFormat object every time it is needed. However, according to this source the fastest way to use DateFormat
in a multi-threaded environment is to use ThreadLocal
. So, our utility class could look like this:
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TimeUtil {
private static final ThreadLocal<DateFormat> FORMAT =
new ThreadLocal<DateFormat>() {
@Override protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String formatDate(Date date) {
return FORMAT.get().format(date);
}
public static Date parse(String dateString) throws ParseException {
return FORMAT.get().parse(dateString);
}
}
Summary
To sum up, if your application works in a concurrent environment, you should always check your classes for thread-safety, especially when using Java Formatters.