Trong nhiều hệ thống Java legacy, đặc biệt các project lâu năm (banking, payment, enterprise systems), chúng ta thường thấy code dạng:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse("2026-03-12 14:30:00");
Tuy nhiên từ Java 8, Oracle đã giới thiệu Java Time API (java.time), bao gồm LocalDate, LocalDateTime, Instant, và DateTimeFormatter.
Trong bài viết này chúng ta sẽ:
- Hiểu tại sao nên bỏ
SimpleDateFormat - So sánh old vs new API
- Hiểu thread-safety issue
- Xây dựng DateUtils production-ready cho Spring Boot
1. Vấn đề của SimpleDateFormat
SimpleDateFormat thuộc package:
java.text
Đây là legacy API từ Java 1.1 (1997).
Ví dụ code thường thấy
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Nhưng đây là một bug tiềm ẩn nghiêm trọng
SimpleDateFormat KHÔNG thread-safe.
2. Thread Safety Problem
Trong hệ thống multi-thread (Spring Boot, Web API, Kafka consumer, batch job), nhiều thread có thể sử dụng cùng một instance.
Ví dụ:
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public String format(Date date) {
return SDF.format(date);
}
Nếu 100 request chạy cùng lúc:
Thread A -> modify calendar
Thread B -> modify calendar
Có thể gây ra:
- format sai
- parse sai
- race condition
- random bugs rất khó debug
Ví dụ output lỗi:
2026-13-72 99:61:00
3. DateTimeFormatter giải quyết vấn đề
Java 8 giới thiệu package:
java.time
DateTimeFormatter được thiết kế:
- immutable
- thread-safe
- functional style
- clear API
4. So sánh API
| Feature | SimpleDateFormat | DateTimeFormatter |
|---|---|---|
| Package | java.text | java.time |
| Introduced | Java 1.1 | Java 8 |
| Thread safe | ❌ No | ✅ Yes |
| Mutable | Yes | No |
| Recommended | ❌ Legacy | ✅ Modern |
5. Ví dụ sử dụng DateTimeFormatter
Parse String → LocalDateTime
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime dateTime = LocalDateTime.parse("2026-03-12 14:30:00", formatter);
Format LocalDateTime → String
String result = LocalDateTime.now().format(formatter);
6. Vấn đề khi migrate sang Java Time
Trong hệ thống thực tế, chúng ta vẫn gặp:
java.util.DateTimestampInstantLocalDateLocalDateTime
Vì vậy cần utility class chuẩn.
7. Production Ready DateUtils for Spring Boot
Dưới đây là một utility class có thể dùng trong enterprise project.
Features
✔ thread-safe
✔ timezone Asia/Ho_Chi_Minh
✔ support
- Date
- LocalDate
- LocalDateTime
- Instant
DateUtils.java
package com.example.common.util;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Date;
public final class DateUtils {
private DateUtils() {}
public static final ZoneId ZONE_ID = ZoneId.of("Asia/Ho_Chi_Minh");
public static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static final DateTimeFormatter ISO_DATE_TIME = DateTimeFormatter.ISO_DATE_TIME;
/*
* ================================
* NOW
* ================================
*/
public static LocalDate today() {
return LocalDate.now(ZONE_ID);
}
public static LocalDateTime now() {
return LocalDateTime.now(ZONE_ID);
}
public static Instant nowInstant() {
return Instant.now();
}
public static Date nowDate() {
return Date.from(Instant.now());
}
/*
* ================================
* FORMAT
* ================================
*/
public static String format(LocalDate date) {
return date.format(DATE_FORMAT);
}
public static String format(LocalDateTime dateTime) {
return dateTime.format(DATE_TIME_FORMAT);
}
public static String format(Date date) {
return DATE_TIME_FORMAT.format(
date.toInstant().atZone(ZONE_ID).toLocalDateTime()
);
}
/*
* ================================
* PARSE
* ================================
*/
public static LocalDate parseDate(String date) {
return LocalDate.parse(date, DATE_FORMAT);
}
public static LocalDateTime parseDateTime(String dateTime) {
return LocalDateTime.parse(dateTime, DATE_TIME_FORMAT);
}
/*
* ================================
* CONVERT
* ================================
*/
public static Date toDate(LocalDateTime localDateTime) {
return Date.from(localDateTime.atZone(ZONE_ID).toInstant());
}
public static Date toDate(LocalDate localDate) {
return Date.from(localDate.atStartOfDay(ZONE_ID).toInstant());
}
public static LocalDateTime toLocalDateTime(Date date) {
return date.toInstant()
.atZone(ZONE_ID)
.toLocalDateTime();
}
public static LocalDate toLocalDate(Date date) {
return date.toInstant()
.atZone(ZONE_ID)
.toLocalDate();
}
public static Instant toInstant(Date date) {
return date.toInstant();
}
public static Date fromInstant(Instant instant) {
return Date.from(instant);
}
}
8. Ví dụ sử dụng trong Spring Boot
Format
String time = DateUtils.format(LocalDateTime.now());
Parse
LocalDateTime time = DateUtils.parseDateTime("2026-03-12 14:30:00");
Convert
Date date = DateUtils.toDate(LocalDateTime.now());
9. Best Practices cho Production
1️⃣ Không dùng SimpleDateFormat static
static SimpleDateFormat sdf = new SimpleDateFormat();
2️⃣ Sử dụng java.time
LocalDate
LocalDateTime
Instant
ZonedDateTime
3️⃣ Chuẩn hoá timezone
Trong production system:
UTC hoặc Asia/Ho_Chi_Minh
4️⃣ DB nên lưu Instant hoặc UTC
Ví dụ:
2026-03-12T07:30:00Z
10. Khi nào vẫn dùng Date
Một số framework vẫn sử dụng:
- JDBC
- JPA
- Jackson legacy
- Oracle driver
Vì vậy cần convert.
Kết luận
SimpleDateFormat là legacy API và không thread-safe, dễ gây lỗi trong các hệ thống concurrent như Spring Boot hoặc microservices.
DateTimeFormatter thuộc Java Time API:
- thread-safe
- immutable
- clean API
- dễ maintain
Vì vậy các project hiện đại nên:
SimpleDateFormat → DateTimeFormatter
Date → LocalDate / LocalDateTime / Instant
✅ TL;DR
| Legacy | Modern |
|---|---|
| Date | LocalDateTime |
| SimpleDateFormat | DateTimeFormatter |
| Calendar | ZonedDateTime |
Be First to Comment