Master Java 8 Date & Time API: Thread‑Safe LocalDate, LocalTime, and LocalDateTime
This article explains why the legacy Date and SimpleDateFormat classes are problematic, introduces Java 8's immutable LocalDate, LocalTime, LocalDateTime and Instant classes, demonstrates thread‑safe creation, formatting, parsing, modification and calculation, and shows practical SpringBoot integration examples.
During project development you often encounter time handling, but are you really using it correctly? Understand why Alibaba's development manual forbids static SimpleDateFormat.
By reading this article you will learn why you need the new Java 8 classes LocalDate, LocalTime, and LocalDateTime, and how to use the new date‑time API for creation, formatting, parsing, calculation and modification.
Why you need LocalDate, LocalTime, LocalDateTime
The old Date class produces unreadable output without formatting. Tue Sep 10 09:34:04 CST 2019 SimpleDateFormat formats dates but is not thread‑safe; its format method uses a shared calendar field without synchronization, causing incorrect results when multiple threads invoke it simultaneously.
private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);
// ...
switch (tag) {
case TAG_QUOTE_ASCII_CHAR:
toAppendTo.append((char)count);
break;
// ...
default:
subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
break;
}
return toAppendTo;
}Because calendar is shared, concurrent threads may overwrite each other's state, leading to wrong formatted results. Both format and parse are unsafe. To achieve thread safety you can either synchronize the calls (which hurts performance) or use a ThreadLocal<SimpleDateFormat> so each thread gets its own instance.
The legacy Date class makes operations such as obtaining year, month, week, or adding days cumbersome, and many of its getter methods (e.g., getYear, getMonth) are deprecated.
Java 8 Date‑Time API Overview
LocalDate
Creation
// current date
LocalDate localDate = LocalDate.now();
// specific date
LocalDate localDate1 = LocalDate.of(2019, 9, 10);Accessing fields
int year = localDate.getYear();
Month month = localDate.getMonth();
int day = localDate.getDayOfMonth();
DayOfWeek dayOfWeek = localDate.getDayOfWeek();LocalTime
Creation
LocalTime localTime = LocalTime.of(13, 51, 10);
LocalTime localTime1 = LocalTime.now();Accessing fields
int hour = localTime.getHour();
int minute = localTime.getMinute();
int second = localTime.getSecond();LocalDateTime
Creation
LocalDateTime now = LocalDateTime.now();
LocalDateTime specific = LocalDateTime.of(2019, Month.SEPTEMBER, 10, 14, 46, 56);
LocalDateTime combined = LocalDateTime.of(localDate, localTime);
LocalDateTime fromDate = localDate.atTime(localTime);
LocalDateTime fromTime = localTime.atDate(localDate);Extracting components
LocalDate datePart = localDateTime.toLocalDate();
LocalTime timePart = localDateTime.toLocalTime();Instant
Creation and epoch values
Instant instant = Instant.now();
long seconds = instant.getEpochSecond();
long millis = instant.toEpochMilli();For simple millisecond timestamps, System.currentTimeMillis() is often more convenient.
Modifying immutable objects
Adding or subtracting time
LocalDateTime dt = LocalDateTime.of(2019, Month.SEPTEMBER, 10, 14, 46, 56);
// add one year
dt = dt.plusYears(1);
// subtract one month
dt = dt.minusMonths(1);Changing specific fields with with
dt = dt.withYear(2020);
dt = dt.with(ChronoField.YEAR, 2022);Time calculations
You can quickly determine the last day of a month, the next weekend, etc., using the fluent API. Example:
LocalDate today = LocalDate.now();
LocalDate firstDayOfYear = today.with(TemporalAdjusters.firstDayOfYear());Formatting
Predefined formatters:
String s1 = localDate.format(DateTimeFormatter.BASIC_ISO_DATE);
String s2 = localDate.format(DateTimeFormatter.ISO_LOCAL_DATE);Custom pattern:
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String s3 = localDate.format(fmt);Parsing
LocalDate d1 = LocalDate.parse("20190910", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate d2 = LocalDate.parse("2019-09-10", DateTimeFormatter.ISO_LOCAL_DATE);Unlike SimpleDateFormat, DateTimeFormatter is thread‑safe.
Quick recap
The new API provides readable, immutable, and thread‑safe alternatives to the old Date and SimpleDateFormat classes, covering all common date‑time needs.
SpringBoot integration
Serialize LocalDateTime as a timestamp
public class LocalDateTimeConverter extends JsonSerializer<LocalDateTime> {
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeNumber(value.toInstant(ZoneOffset.of("+8")).toEpochMilli());
}
}Apply with annotation:
@JsonSerialize(using = LocalDateTimeConverter.class)
protected LocalDateTime gmtModified;Serialize with a specific pattern
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
protected LocalDateTime gmtModified;Parse incoming strings
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
protected LocalDateTime gmtModified;Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
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.
