Java's SimpleDateFormat is not thread-safe, Use carefully in multi-threaded environments

SimpleDateFormat 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 a lock.

1. Create separate instances of SimpleDateFormat for every thread

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)

2. Share the same instance of SimpleDateFormat but synchronize concurrent access


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…