Java Date and Time Formatting: Methods, Thread Safety, and Best Practices
This article compares three common Java date‑time formatting approaches—SimpleDateFormat, Java 8’s DateTimeFormatter, and Apache Commons Lang3’s DateFormatUtils—examines thread‑safety issues, provides code examples, and offers conversion utilities between Date, LocalDate, and LocalDateTime for robust backend development.
Introduction
In everyday project development, formatting dates and times is a frequent requirement. While many consider it trivial, the choice of formatting method can greatly affect performance and thread safety.
Common Date Formatting Methods
The article presents three typical ways to format dates in Java:
Using SimpleDateFormat (pre‑Java 8)
Using Java 8's DateTimeFormatter
Using Apache Commons Lang3's DateFormatUtils
Analysis of SimpleDateFormat
Method One: Beginner Implementation
Many beginners use SimpleDateFormat , which is not thread‑safe because it shares a mutable Calendar instance across threads.
Reason
In a multithreaded environment, concurrent calls to SimpleDateFormat.format() modify the shared calendar, leading to incorrect results.
public static void main(String[] args) {
Date now = new Date();
String strDateFormat = "yyyy-MM-dd HH:mm:ss";
// Beginner implementation
SimpleDateFormat f = new SimpleDateFormat(strDateFormat);
System.out.println("SimpleDateFormat:" + f.format(now));
// Java 8 implementation
LocalDateTime localDateTime = now.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
String result = localDateTime.format(DateTimeFormatter.ofPattern(strDateFormat));
System.out.println("DateTimeFormatter:" + result);
// Commons‑Lang3 implementation
result = DateFormatUtils.format(now, strDateFormat);
System.out.println("DateFormatUtils:" + result);
}A multithreaded test demonstrates occasional false outputs, confirming thread‑unsafe behavior.
public class SimpleDateFormatTest {
private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(1000), new MyThreadFactory("SimpleDateFormatTest"));
public void test() {
while (true) {
poolExecutor.execute(new Runnable() {
@Override
public void run() {
String dateString = simpleDateFormat.format(new Date());
try {
Date parseDate = simpleDateFormat.parse(dateString);
String dateString2 = simpleDateFormat.format(parseDate);
System.out.println(dateString.equals(dateString2));
} catch (ParseException e) {
e.printStackTrace();
}
}
});
}
}
}Solutions
Declare SimpleDateFormat as a local variable.
Synchronize access with a lock, e.g., synchronized(lock) .
Use ThreadLocal to give each thread its own SimpleDateFormat instance.
Method Two: Java 8 Advanced Implementation
Java 8 recommends DateTimeFormatter , which is thread‑safe and offers a rich API.
LocalDateTime localDateTime = LocalDateTime.now();
String result = localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println("DateTimeFormatter:" + result);The new API works with LocalDate (date only) and LocalDateTime (date and time).
Method Three: Commons‑Lang3 Implementation
Apache Commons Lang3 provides DateFormatUtils , which internally uses FastDateFormat for efficient, thread‑safe formatting.
Date now = new Date();
String strDateFormat = "yyyy-MM-dd HH:mm:ss";
String result = DateFormatUtils.format(now, strDateFormat);
System.out.println("DateFormatUtils:" + result);Dependency:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>Conversion Utilities Between Date, LocalDate, and LocalDateTime
public static LocalDate date2LocalDate(Date date) {
if (date == null) return null;
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}
public static LocalDateTime date2LocalDateTime(Date date) {
if (date == null) return null;
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
}
public static Date localDate2Date(LocalDate localDate) {
if (localDate == null) return null;
ZonedDateTime zonedDateTime = localDate.atStartOfDay(ZoneId.systemDefault());
return Date.from(zonedDateTime.toInstant());
}
public static Date localDateTime2Date(LocalDateTime localDateTime) {
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
}Summary
The article introduces three common Java date‑formatting techniques, highlights the thread‑unsafe nature of SimpleDateFormat , recommends the thread‑safe DateTimeFormatter for Java 8+, and suggests using Apache Commons Lang3's DateFormatUtils for a simple, efficient, and memory‑friendly solution, optionally extending it in a custom DateUtils class.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.