252

Please tell with a code example why is SimpleDateFormat not threadsafe. What is the problem in this class? Is The problem with format function of SimpleDateFormat? Please give a code which demonstrates this fault in class.

FastDateFormat is threadsafe. Why? what is the difference b/w the SimpleDateFormat and FastDateFormat?

Please explain with a code which demonstrates this issue?

Flow
  • 22,048
  • 13
  • 91
  • 147
Vivek Sharma
  • 2,527
  • 2
  • 12
  • 7
  • 2
    FastDateFormat is a commons-lang class: https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/time/FastDateFormat.html – Steve N Sep 02 '14 at 16:33
  • 4
    Most Developers understand that for most classes that are not thread safe, that this is due to concurrently changing state. Once a Format is established, formatting a Date should not change state. Simply documenting this in official documentation as not thread-safe is not enough. It should be explicitly documented that even the format method is not thread-safe if it maintains temporary state in instance variables. Declaring it as static is not just a rookie mistake. Analogy can be made between modifying a collection (put) vs accessing a collection (get). – YoYo Nov 02 '14 at 05:26
  • 1
    Just a short real story: I've running a cloud based application for about 8 years, with nearly 100% uptime. There were a strange individual error recently related to parsing dates. One parsed date was wrong. During a code review I discovered that SimpleDateFormat was used wrong and it was a thread-safety issue. One error for 8 years! Of course I'm going to fix it. – pcjuzer Oct 13 '18 at 13:49
  • I made the same error too, expecting the `format` and `parse` methods to be threadsafe once the format and timezone are set. Currently I am searching and fixing all those SimpleDateFormat usages in our codebase :/ – Erich Kitzmueller Aug 19 '20 at 07:28
  • This took me some time to track down and has cost the client a lot a significant amount of money. Simply put don't use SimpleDateFormat it is not thread-safe use DateTimeFormatter – mujib ishola Nov 03 '20 at 15:30
  • Never use `DateFormat`, `SimpleDateFormat`, `Date`, and `Calendar` classes. These terrible classes are all legacy now. They were supplanted years ago by the modern *java.time* classes defined in JSR 310. The *java.time* classes are [thread-safe](https://en.wikipedia.org/wiki/Thread_safety) by design, using [immutable objects](https://en.wikipedia.org/wiki/Immutable_object). – Basil Bourque Mar 20 '21 at 23:36

9 Answers9

263

SimpleDateFormat stores intermediate results in instance fields. So if one instance is used by two threads they can mess each other's results.

Looking at the source code reveals that there is a Calendar instance field, which is used by operations on DateFormat / SimpleDateFormat.

For example parse(..) calls calendar.clear() initially and then calendar.add(..). If another thread invokes parse(..) before the completion of the first invocation, it will clear the calendar, but the other invocation will expect it to be populated with intermediate results of the calculation.

One way to reuse date formats without trading thread-safety is to put them in a ThreadLocal - some libraries do that. That's if you need to use the same format multiple times within one thread. But in case you are using a servlet container (that has a thread pool), remember to clean the thread-local after you finish.

To be honest, I don't understand why they need the instance field, but that's the way it is. You can also use joda-time DateTimeFormat which is threadsafe.

Hearen
  • 6,019
  • 2
  • 36
  • 50
Bozho
  • 554,002
  • 136
  • 1,025
  • 1,121
  • 67
    They don't *need* the instance field; it is undoubtedly the result of sloppy programming in a misguided attempt at efficiency. The really mind-boggling thing is that this trap-door wasn't nailed shut long long ago. I think the real answer is to avoid java.util.Date and Calendar. – kevin cline Aug 16 '12 at 19:59
  • 4
    Has this been fixed in JDK8? If no, then why not? – dzieciou Oct 22 '15 at 08:36
  • 30
    this hasn't been fixed in JDK8 per se. but JDK8 introduces the new java.time package, including DateTimeFormatter which is threadsafe. – james turner Nov 18 '15 at 18:03
  • 3
    It cannot be "fixed" ever, without breaking backwards compatibility. It's better to leave it alone, and let new code just use the newer, thread-safe alternatives. . – whirlwin Mar 30 '17 at 13:26
  • @whirlwin it can easily be fixed since it is implementation detail... – Enerccio Nov 01 '17 at 09:18
  • @Enerccio It is not a detail, because it is poorly designed from the bottom up. And completely rewriting it is too risky too. – whirlwin Nov 01 '17 at 12:33
  • 3
    @whirlwin if you don't change the interface... – Enerccio Nov 01 '17 at 13:24
  • 2
    @Enerccio ...then in theory, yes, that won't break anything. In practice, I _guarantee_ that in the decades (yes, literally decades, SimpleDateFormat has been around since 1.1, which was released in 1996, more than 20 years ago) since, plenty of people have depended on the lack of thread-safety for something or other. Is it horrible practice? Yes, obviously. Does that mean it doesn't happen? I wish. – Fund Monica's Lawsuit Sep 11 '18 at 18:47
  • 1
    @FundMonica'sLawsuit The lack of thread safety only leads to unpredictable errors appearing every now and then (when used in a not thread-safe manner), it's hard to imagine a program that *depends* on that. – Erich Kitzmueller Aug 19 '20 at 07:21
65

SimpleDateFormat is a concrete class for formatting and parsing dates in a locale-sensitive manner.

From the JavaDoc,

But Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

To make the SimpleDateFormat class thread-safe, look at the following approaches :

  • Create a new SimpleDateFormat instance each time you need to use one. Although this is thread safe, it is the slowest possible approach.
  • Use synchronization. This is a bad idea because you should never choke-point your threads on a server.
  • Use a ThreadLocal. This is the fastest approach of the 3 (see http://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html).
sgokhales
  • 49,762
  • 33
  • 123
  • 158
  • 9
    This looks like a good summary but I disagree with the author's second point. Somehow I doubt synchronizing a date format is going to be the choke point on your server. Is this, per Knuth, one of the 3% of cases where premature optimization is required, or does it fall into the 97%, where "we should forget about small inefficiencies"? Now, I've seen folks, with custom web frameworks, wrapping controller in a synchronized block, and thus all access beyond including database calls, business logic - and then spending a huge effort on performance testing. No wonder there, they are in the 3%. – michaelok Mar 29 '12 at 21:57
  • 9
    @michaelok I have to concur! I think it is just the other way around - using one Single Dateformatter instead of creating a new one whenever you need it is premature optimization. You should do the Easy thing first: Just use a new Instance whenever you need it. - And only if this becomes a performance-problem (Memory, GBC) then you should think about a shared instance - but remember: Anything you share between Threads can become a silent race-condition waiting to suckerpunch you. – Falco Mar 26 '14 at 17:05
  • 4
    And btw. an easy point could be one Thread getting stuck in the Routine of the Dateformatter because of whatever Problems - and suddenly EACH AND EVERY Thread on your webserver would get stuck, when they try to access the DateFormatter... DED ;-) – Falco Mar 26 '14 at 17:08
  • You can create a new instance or clone it that can be a little bit faster. – borjab Jul 31 '14 at 09:33
  • @michaelok It was a problem with us today. – Chad Apr 21 '17 at 04:22
  • What was the problem - you ran into a chokepoint due to synchronizing the date format? Or an issue with thread-safety? One thing as mentioned above by James Turner is that Java 8's Formatter _is_ thread safe: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html – michaelok Apr 21 '17 at 16:04
  • the question was WHY it is not thread-safe not HOW to make it thread-safe. – Christophe Roussy Jul 24 '17 at 09:13
54

DateTimeFormatter in Java 8 is immutable and thread-safe alternative to SimpleDateFormat.

TheKojuEffect
  • 17,034
  • 16
  • 77
  • 113
  • yes but you'll have to go with a Temporal (LocalDate, LocalDateTime, ...) instead of ````java.util.Date```` which SimpleDateFormat uses. – Saad Benbouzid Nov 22 '17 at 13:28
  • 2
    @SaadBenbouzid Consider that an advantage. The modern classes are so much nicer to work with than the outmoded `Date` class and offer much more possibilities. – Ole V.V. Mar 03 '18 at 16:17
  • 1
    Yep and have problems with offset. – 3Qn Oct 29 '18 at 11:33
34

ThreadLocal + SimpleDateFormat = SimpleDateFormatThreadSafe

package com.foocoders.text;

import java.text.AttributedCharacterIterator;
import java.text.DateFormatSymbols;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

public class SimpleDateFormatThreadSafe extends SimpleDateFormat {

    private static final long serialVersionUID = 5448371898056188202L;
    ThreadLocal<SimpleDateFormat> localSimpleDateFormat;

    public SimpleDateFormatThreadSafe() {
        super();
        localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
            protected SimpleDateFormat initialValue() {
                return new SimpleDateFormat();
            }
        };
    }

    public SimpleDateFormatThreadSafe(final String pattern) {
        super(pattern);
        localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
            protected SimpleDateFormat initialValue() {
                return new SimpleDateFormat(pattern);
            }
        };
    }

    public SimpleDateFormatThreadSafe(final String pattern, final DateFormatSymbols formatSymbols) {
        super(pattern, formatSymbols);
        localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
            protected SimpleDateFormat initialValue() {
                return new SimpleDateFormat(pattern, formatSymbols);
            }
        };
    }

    public SimpleDateFormatThreadSafe(final String pattern, final Locale locale) {
        super(pattern, locale);
        localSimpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
            protected SimpleDateFormat initialValue() {
                return new SimpleDateFormat(pattern, locale);
            }
        };
    }

    public Object parseObject(String source) throws ParseException {
        return localSimpleDateFormat.get().parseObject(source);
    }

    public String toString() {
        return localSimpleDateFormat.get().toString();
    }

    public Date parse(String source) throws ParseException {
        return localSimpleDateFormat.get().parse(source);
    }

    public Object parseObject(String source, ParsePosition pos) {
        return localSimpleDateFormat.get().parseObject(source, pos);
    }

    public void setCalendar(Calendar newCalendar) {
        localSimpleDateFormat.get().setCalendar(newCalendar);
    }

    public Calendar getCalendar() {
        return localSimpleDateFormat.get().getCalendar();
    }

    public void setNumberFormat(NumberFormat newNumberFormat) {
        localSimpleDateFormat.get().setNumberFormat(newNumberFormat);
    }

    public NumberFormat getNumberFormat() {
        return localSimpleDateFormat.get().getNumberFormat();
    }

    public void setTimeZone(TimeZone zone) {
        localSimpleDateFormat.get().setTimeZone(zone);
    }

    public TimeZone getTimeZone() {
        return localSimpleDateFormat.get().getTimeZone();
    }

    public void setLenient(boolean lenient) {
        localSimpleDateFormat.get().setLenient(lenient);
    }

    public boolean isLenient() {
        return localSimpleDateFormat.get().isLenient();
    }

    public void set2DigitYearStart(Date startDate) {
        localSimpleDateFormat.get().set2DigitYearStart(startDate);
    }

    public Date get2DigitYearStart() {
        return localSimpleDateFormat.get().get2DigitYearStart();
    }

    public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) {
        return localSimpleDateFormat.get().format(date, toAppendTo, pos);
    }

    public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
        return localSimpleDateFormat.get().formatToCharacterIterator(obj);
    }

    public Date parse(String text, ParsePosition pos) {
        return localSimpleDateFormat.get().parse(text, pos);
    }

    public String toPattern() {
        return localSimpleDateFormat.get().toPattern();
    }

    public String toLocalizedPattern() {
        return localSimpleDateFormat.get().toLocalizedPattern();
    }

    public void applyPattern(String pattern) {
        localSimpleDateFormat.get().applyPattern(pattern);
    }

    public void applyLocalizedPattern(String pattern) {
        localSimpleDateFormat.get().applyLocalizedPattern(pattern);
    }

    public DateFormatSymbols getDateFormatSymbols() {
        return localSimpleDateFormat.get().getDateFormatSymbols();
    }

    public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) {
        localSimpleDateFormat.get().setDateFormatSymbols(newFormatSymbols);
    }

    public Object clone() {
        return localSimpleDateFormat.get().clone();
    }

    public int hashCode() {
        return localSimpleDateFormat.get().hashCode();
    }

    public boolean equals(Object obj) {
        return localSimpleDateFormat.get().equals(obj);
    }

}

https://gist.github.com/pablomoretti/9748230

Pablo Moretti
  • 1,304
  • 13
  • 9
  • 6
    I have serious doubts if the overhead of thread lookup and synchronization is not bigger than the cost of creating a new instance each time – Jakub Bochenski Dec 13 '17 at 19:46
  • 1
    @JakubBochenski Here is a post that list the comparison of different approaches. It looks like ThreadLocal approach yields the best performance. https://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html – David Ruan Apr 23 '19 at 20:58
  • @DavidRuan thanks, but to quote the top comment on that article: `Could u please provide the source code and the testing code?`. Not knowing if it was benchmarked properly it's just a random graph on the internet. – Jakub Bochenski Apr 24 '19 at 11:50
  • Problem with this solution is it allows to manipulate the `SimpleDateFormat` which can result in a strange state! This is inconsistent and not thread safe. If the `SimpleDateFormat` was immutable this solution woubld be smart - https://gist.github.com/pablomoretti/9748230#gistcomment-3758032 – CodingSamples May 26 '21 at 15:17
15

Release 3.2 of commons-lang will have FastDateParser class that is a thread-safe substitute of SimpleDateFormat for Gregorian calendar. See LANG-909 for more information.

dma_k
  • 9,513
  • 13
  • 68
  • 122
10

Here is the example which results in a strange error. Even Google gives no results:

public class ExampleClass {

private static final Pattern dateCreateP = Pattern.compile("Дата подачи:\\s*(.+)");
private static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss dd.MM.yyyy");

public static void main(String[] args) {
    ExecutorService executor = Executors.newFixedThreadPool(100);
    while (true) {
        executor.submit(new Runnable() {
            @Override
            public void run() {
                workConcurrently();
            }
        });
    }
}

public static void workConcurrently() {
    Matcher matcher = dateCreateP.matcher("Дата подачи: 19:30:55 03.05.2015");
    Timestamp startAdvDate = null;
    try {
        if (matcher.find()) {
            String dateCreate = matcher.group(1);
            startAdvDate = new Timestamp(sdf.parse(dateCreate).getTime());
        }
    } catch (Throwable th) {
        th.printStackTrace();
    }
    System.out.print("OK ");
}
}

And result :

OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK OK java.lang.NumberFormatException: For input string: ".201519E.2015192E2"
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.nonscalper.webscraper.processor.av.ExampleClass.workConcurrently(ExampleClass.java:37)
at com.nonscalper.webscraper.processor.av.ExampleClass$1.run(ExampleClass.java:25)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Necreaux
  • 8,456
  • 7
  • 22
  • 43
user2602807
  • 1,011
  • 2
  • 11
  • 19
5

Here’s an example defines a SimpleDateFormat object as a static field. When two or more threads access “someMethod” concurrently with different dates, they can mess with each other’s results.

    public class SimpleDateFormatExample {
         private static final SimpleDateFormat simpleFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

         public String someMethod(Date date) {
            return simpleFormat.format(date);
         }
    }

You can create a service like below and use jmeter to simulate concurrent users using the same SimpleDateFormat object formatting different dates and their results will be messed up.

public class FormattedTimeHandler extends AbstractHandler {

private static final String OUTPUT_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";
private static final String INPUT_TIME_FORMAT = "yyyy-MM-ddHH:mm:ss";
private static final SimpleDateFormat simpleFormat = new SimpleDateFormat(OUTPUT_TIME_FORMAT);
// apache commons lang3 FastDateFormat is threadsafe
private static final FastDateFormat fastFormat = FastDateFormat.getInstance(OUTPUT_TIME_FORMAT);

public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException {

    response.setContentType("text/html;charset=utf-8");
    response.setStatus(HttpServletResponse.SC_OK);
    baseRequest.setHandled(true);

    final String inputTime = request.getParameter("time");
    Date date = LocalDateTime.parse(inputTime, DateTimeFormat.forPattern(INPUT_TIME_FORMAT)).toDate();

    final String method = request.getParameter("method");
    if ("SimpleDateFormat".equalsIgnoreCase(method)) {
        // use SimpleDateFormat as a static constant field, not thread safe
        response.getWriter().println(simpleFormat.format(date));
    } else if ("FastDateFormat".equalsIgnoreCase(method)) {
        // use apache commons lang3 FastDateFormat, thread safe
        response.getWriter().println(fastFormat.format(date));
    } else {
        // create new SimpleDateFormat instance when formatting date, thread safe
        response.getWriter().println(new SimpleDateFormat(OUTPUT_TIME_FORMAT).format(date));
    }
}

public static void main(String[] args) throws Exception {
    // embedded jetty configuration, running on port 8090. change it as needed.
    Server server = new Server(8090);
    server.setHandler(new FormattedTimeHandler());

    server.start();
    server.join();
}

}

The code and jmeter script can be downloaded here .

ylu
  • 797
  • 7
  • 5
3

Here is a code example that proves the fault in the class. I've checked: the problem occurs when using parse and also when you are only using format.

Hans-Peter Störr
  • 22,852
  • 27
  • 96
  • 134
  • This code sample has few flaws: `NumberFormatException` / `ArrayIndexOutOfBoundsException` can also be thrown due to concurrency issue and they "silently" kill the thread. Also threads are not joined, which is not good. Check classes in [`LANG-909`](https://issues.apache.org/jira/browse/LANG-909) – I think they look better. – dma_k Aug 08 '13 at 09:37
  • 1
    @dma_k I don't quite see why you would join threads in test code whose sole purpose it is to fail and die. :-) Anyway: I didn't want to recommend the ThreadSafeSimpleDateFormat from the blog (you are right: there are much better solutions) but to point to the failure demonstration. – Hans-Peter Störr Aug 09 '13 at 13:41
  • 1
    This is more important for Unix tests, where died threads will not impact the result of test itself. Yes, something will be printed to console, but from exception one cannot recognize is it due to error in program (format / input data) or concurrency issue. The code is fine by itself, my comment is for those who will copy/paste it and use under different conditions. – dma_k Aug 11 '13 at 15:35
-3

If you want to use the same date format among multiple threads, declare it as a static and synchronize on the instance variable when using it...

static private SimpleDateFormat sdf = new SimpleDateFormat("....");

synchronized(sdf)
{
   // use the instance here to format a date
}


// The above makes it thread safe
Rodney P. Barbati
  • 1,399
  • 17
  • 16