java.time (Date and Time API)
Master Java's modern date-time API: LocalDate, LocalDateTime, Instant, Duration, Period, and ZonedDateTime for robust time handling.
Master Java's modern date-time API: LocalDate, LocalDateTime, Instant, Duration, Period, and ZonedDateTime for robust time handling.
Introduction
Java 8 introduced the java.time package to address the well-known deficiencies of java.util.Date and java.util.Calendar. The new API is immutable, thread-safe, and follows a clear domain-driven design with separate types for different time concepts.
When to Use
| Use Case | Recommended Type |
|---|---|
| Birthdays, holidays, birthdays | LocalDate |
| Timestamp with no timezone context | Instant |
| Precise elapsed durations | Duration |
| Age calculation, calendar periods | Period |
| Time with timezone awareness | ZonedDateTime / OffsetDateTime |
| Human-readable date and time | LocalDateTime |
When NOT to Use
- Legacy interop: Use
java.util.Dateonly when forced by legacy APIs. - Database columns: Use JDBC adapters rather than manual conversion; prefer
LocalDateTimeforTIMESTAMP WITH TIME ZONE. - Serializing to JSON: Configure
ObjectMapperwithJavaTimeModuleinstead of manual parsing.
Core Types Diagram
classDiagram
class Temporal~T~ {
<<interface>>
}
class TemporalAmount {
<<interface>>
}
class LocalDate {
+now() LocalDate
+of(int year, int month, int day) LocalDate
+plusDays(long) LocalDate
+minusDays(long) LocalDate
+isAfter(LocalDate) boolean
}
class LocalDateTime {
+now() LocalDateTime
+of(int year, int month, int day, int hour, int min) LocalDateTime
+toLocalDate() LocalDate
+toLocalTime() LocalTime
}
class Instant {
+now() Instant
+ofEpochSecond(long) Instant
+plus(Duration) Instant
+minus(Duration) Instant
+toEpochMilli() long
}
class ZonedDateTime {
+now(ZoneId) ZonedDateTime
+of(LocalDateTime, ZoneId) ZonedDateTime
+withZoneSameInstant(ZoneId) ZonedDateTime
}
class Duration {
+ofDays(long) Duration
+ofHours(long) Duration
+between(Temporal, Temporal) Duration
+toMillis() long
}
class Period {
+ofDays(int) Period
+ofMonths(int) Period
+between(LocalDate, LocalDate) Period
+getDays() int
}
Temporal~T~ <|-- LocalDate
Temporal~T~ <|-- LocalDateTime
Temporal~T~ <|-- Instant
Temporal~T~ <|-- ZonedDateTime
TemporalAmount <|-- Duration
TemporalAmount <|-- Period
LocalDateTime --> LocalDate : toLocalDate()
ZonedDateTime --> LocalDateTime : composes
Code Examples
LocalDate — Birthdays and Calendar Dates
LocalDate is a calendar date with year, month, and day only. Pick it for birthdays, expiration dates, billing cycles, hire dates, or any date where the wall-clock time is irrelevant. It stores no time, timezone, or instant information — it is the lightest date type in java.time and the right default for business-domain date fields.
The API uses a fluent builder pattern. LocalDate.of(year, month, day) builds a specific date. now() reads the current date from the system clock in the default timezone. Arithmetic methods like plusDays(), minusMonths(), and plusYears() return new LocalDate instances rather than mutating the original — all java.time types are immutable. Comparison methods isAfter(), isBefore(), and isEqual() handle date ordering without requiring you to work with epoch days directly.
import java.time.LocalDate;
import java.time.Month;
LocalDate today = LocalDate.now();
LocalDate javaBirthday = LocalDate.of(1995, Month.MAY, 23);
LocalDate endOfYear = LocalDate.of(2026, 12, 31);
// Difference in days
long daysBetween = javaBirthday.until(today).getDays();
System.out.println("Java is " + daysBetween + " days old");
// Adding/subtracting
LocalDate nextWeek = today.plusWeeks(1);
LocalDate lastMonth = today.minusMonths(1);
// Comparison
if (today.isAfter(javaBirthday)) {
System.out.println("Java has been around for a while");
}
Instant — Machine Timestamps
Instant is a point in time on the UTC timeline — nanosecond precision since the epoch 1970-01-01T00:00:00Z. Unlike LocalDateTime which ignores timezone, Instant always refers to the same moment anywhere in the world. It is the right type for machine timestamps, log event times, metric data points, and any time coordinate that must be consistent across distributed systems. When you need to correlate events across different machines or timezones, Instant is the answer.
The API stays minimal. Instant.now() captures the current moment. Instant.ofEpochSecond(long) and Instant.ofEpochMilli(long) reconstruct an instant from epoch values. Arithmetic uses Duration — instant.plus(Duration.ofMinutes(5)) or instant.minus(Duration.ofHours(1)). To display an Instant in a specific timezone, convert it to ZonedDateTime with atZone(ZoneId). For storage and transmission, toEpochMilli() serializes an instant to a long that any database or message queue can store.
import java.time.Instant;
import java.time.Duration;
Instant startup = Instant.now();
// ... app runs ...
Instant now = Instant.now();
Duration elapsed = Duration.between(startup, now);
System.out.println("Elapsed: " + elapsed.toMillis() + "ms");
// Convert to/from epoch millis
long epochMillis = Instant.now().toEpochMilli();
Instant restored = Instant.ofEpochMilli(epochMillis);
ZonedDateTime — Timezone-Aware Timestamps
ZonedDateTime adds a timezone to a LocalDateTime, producing a fully timezone-aware timestamp. The timezone is a ZoneId — a string like "Asia/Tokyo", "Europe/Paris", or "UTC" — that encodes the rules for converting between local wall-clock time and UTC offset, including DST transitions. When you need a timestamp that means the same wall-clock time in a specific region, ZonedDateTime is the type to use.
The most common datetime mistake is using LocalDateTime for scheduled events. LocalDateTime.now() returns a value that differs depending on where you are in the world — two servers in Tokyo and London will report different “now” values even at the exact same UTC moment. ZonedDateTime.now(ZoneId.of("Asia/Tokyo")) produces a value that is unambiguous and correctly represents the scheduled local time. For scheduled tasks, log shipping, audit timestamps, and any event that must fire at a specific local time in a specific region, use ZonedDateTime.
import java.time.ZoneId;
import java.time.ZonedDateTime;
ZonedDateTime utcNow = ZonedDateTime.now(ZoneId.of("UTC"));
ZonedDateTime tokyoTime = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
// Convert between zones
ZonedDateTime parisScheduled = ZonedDateTime.of(2026, 6, 15, 14, 0, 0, 0, ZoneId.of("Europe/Paris"));
ZonedDateTime tokyoConverted = parisScheduled.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
System.out.println("Paris 14:00 = Tokyo " + tokyoConverted.getHour() + ":00");
Duration and Period — Elapsed Time
Duration and Period both express elapsed time, but they measure it in different units and serve different purposes. Duration models elapsed time as seconds and nanoseconds — use it when you need precise mechanical measurement: how long did this operation take, what is the latency between these two events, how many seconds until timeout. Duration is the right choice for performance measurement, animation timing, and any scenario where wall-clock accuracy in seconds or nanoseconds is what you need.
Period models elapsed time as calendar units — days, months, years. Use it when you are doing calendar arithmetic: how old is this person, what date is three months from now, does this subscription renew on the same day next month. Period knows that a month has a different number of days and that adding one month to January 31 lands in February. When the question is “when” rather than “how long,” Period is the right choice. Near DST transitions, a Period of one day may represent 23 or 25 hours of wall-clock time, whereas a Duration of one day is always exactly 86,400 seconds.
import java.time.Duration;
import java.time.Period;
import java.time.LocalDate;
Duration oneHour = Duration.ofHours(1);
Duration halfHour = Duration.ofMinutes(30);
// Combine durations
Duration total = oneHour.plus(halfHour); // 90 minutes
// Period for calendar math
LocalDate start = LocalDate.of(2026, 1, 1);
LocalDate end = LocalDate.of(2026, 5, 23);
Period age = Period.between(start, end);
System.out.println("Months: " + age.getMonths() + ", Days: " + age.getDays());
Date Formatting and Parsing
DateTimeFormatter is the formatting workhorse of java.time. Unlike SimpleDateFormat, every DateTimeFormatter is immutable and thread-safe. You set the pattern and Locale at construction, and that is final.
The API breaks down into three practical layers. Static pre-defined formatters (ISO_LOCAL_DATE, ISO_LOCAL_TIME, ISO_LOCAL_DATE_TIME) handle machine-readable formats with no setup. Call ofPattern(String pattern) to build custom layouts from pattern symbols (yyyy for year, MM for month, dd for day, HH for hour, mm for minute). Then ofLocalizedDateTime(FormatStyle) layers in locale-aware formatting. FormatStyle.MEDIUM gives “Jan 23, 2026” in US English but “23 Jan 2026” in UK English.
Parsing works similarly. parse(CharSequence) alone assumes ISO_LOCAL_DATE_TIME. For anything else, pass the formatter: LocalDateTime.parse("23/05/2026 14:30", custom). Malformed input throws DateTimeParseException — guard untrusted input with try-catch. For user-facing fields that may arrive in flexible formats, add withResolverStyle(ResolverStyle.LENIENT) so “January 1, 2026” parses correctly even when the pattern expects “01/01/2026”.
import java.time.format.DateTimeFormatter;
import java.time.LocalDateTime;
DateTimeFormatter iso = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
DateTimeFormatter custom = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
LocalDateTime now = LocalDateTime.now();
String formatted = now.format(custom);
System.out.println(formatted); // e.g., 23/05/2026 14:30
LocalDateTime parsed = LocalDateTime.parse("23/05/2026 14:30", custom);
Failure Scenarios
| Scenario | Problem | Solution |
|---|---|---|
LocalDate.of(2026, 2, 30) | DateTimeException — invalid day for February | Validate day range: day > 0 && day <= month.length(year % 4 == 0) |
Instant.parse("2026-02-30T10:00") | DateTimeParseException | Always parse through a formatter with lenient mode if input is untrusted |
Duration.between(date1, date2) | java.time.temporal.UnsupportedTemporalTypeException | Duration works with Instant, LocalDateTime, OffsetTime; use Period for LocalDate |
ZonedDateTime.now(ZoneId.of("Mars")) | ZoneRulesException | Validate zone ID against ZoneId.getAvailableZoneIds() |
Race condition on cached ZoneId | Stale zone data | Do not cache ZoneId instances across JVM restarts |
Trade-off Table
| Aspect | java.time | java.util.Date | java.util.Calendar |
|---|---|---|---|
| Thread safety | Immutable, thread-safe | Mutable, not thread-safe | Mutable, not thread-safe |
| API clarity | Domain-specific types | Single ambiguous type | Complex, inconsistent |
| Timezone handling | First-class ZoneId | Manual offset management | Partial support |
| Performance | Slight overhead for immutability | Fast but unsafe | Slow and unsafe |
| New features | Streams, parsing, formatting | Legacy only | Legacy only |
Observability Checklist
// Instrumenting time operations for observability
import java.time.Instant;
import java.time.Duration;
public class TimedOperation {
private final Instant start;
public TimedOperation() {
this.start = Instant.now();
}
public long elapsedMillis() {
return Duration.between(start, Instant.now()).toMillis();
}
// Log with structured fields for observability platforms
public void logDuration(String operation) {
System.out.println("operation=" + operation +
" duration_ms=" + elapsedMillis() +
" timestamp=" + Instant.now());
}
}
- Measure elapsed time with
Duration.between(start, end)and expose as a metric. - Use
Instantfor log timestamps rather thanSystem.currentTimeMillis(). - Validate timezone IDs at startup to fail fast on misconfiguration.
- Store
Instantin persistence layers, notLocalDateTime, to preserve the moment in time. - Log parsing failures with the raw input string for debugging.
Security Notes
- Time-of-check to time-of-use (TOCTOU): Immutable types eliminate some TOCTOU races but calendar operations still require atomic validation.
- Timezone spoofing: If user input sets the timezone, validate it against an allowlist of permitted zones.
- Deserialization attacks: Malformed date strings in untrusted input can cause
DateTimeParseException; catch and log securely without leaking stack traces. - Leap second handling:
Instanttreats leap seconds as a 1-second increment, not as a special case — do not rely onInstantfor financial time calculations.
Pitfalls
- Confusing
DurationwithPeriod:Duration.ofDays(1)creates a 24-hour duration, whilePeriod.ofDays(1)represents a calendar day which may have different length near DST transitions. - Forgetting
ZonedDateTimefor scheduled tasks:LocalDateTime.now()returns a time without timezone — two users in different zones see different “now”. - Immutability surprises: Methods like
plusDays()return a new instance — the original is unchanged. Assign the result. - Epoch overflow:
Instant.MAXis year+1,000,000,000;Instant.MINis year-1,000,000,000. Unlikely to hit, but be aware. LocalDateTimetoZonedDateTimeis ambiguous:LocalDateTime.of(2026, 3, 29, 2, 30)during DST spring-forward has no valid UTC equivalent — this throws an exception.
Quick Recap
java.timeis immutable, thread-safe, and domain-driven.LocalDatefor calendar dates,LocalDateTimefor naive datetime,Instantfor UTC timestamps.ZonedDateTimefor timezone-aware scheduling; useZoneIdto define the zone.Durationfor precise elapsed time,Periodfor calendar-based elapsed time.- Format with
DateTimeFormatter; parse withLocalDateTime.parse(str, formatter). - Always use
Instantfor logging and metric timestamps. - Prefer
Duration.between()over manual subtraction for elapsed time.
Interview Questions
Further Reading
- MDN: Working with dates and times in JavaScript — cross-language perspective on temporal handling pitfalls
- Baeldung: Java Time API Guide — comprehensive coverage of
java.timepatterns - Oracle: Date Time Tutorial — official Java documentation for date-time fundamentals
- Stack Overflow: java.time ZonedDateTime DST handling — common DST edge cases and solutions
- JEP 302: Lambda Leftovers — background on Java evolution influencing
java.timedesign decisions - Text Formatting — String formatting and the java.text package
- java.util.Objects — utility class often used alongside date/time handling
Conclusion
The java.time package addresses everything wrong with java.util.Date and java.util.Calendar — mutability, thread-safety issues, confusing API design, and zero-based month indexing. The new API is immutable by design, which makes it safe to share across threads and predictable across time-zone boundaries. Once you internalize the domain model (LocalDate for calendar days, Instant for machine timestamps, ZonedDateTime for scheduled events), you will find date-time code becomes significantly shorter and less bug-prone.
The most important decision in java.time is choosing the right type for the job. LocalDate stores a calendar date with no time component — use it for birthdays, expiration dates, and any date that is inherently calendar-aligned. Instant stores a point in UTC time — use it for log timestamps, metric events, and any moment that must be preserved across time zones. ZonedDateTime adds a time zone to a local date-time — use it for scheduled events that must fire at a specific wall-clock time in a specific region.
Duration vs Period is another critical distinction. Duration measures elapsed time in seconds and nanoseconds between two TemporalAccessor points — it is appropriate for measuring program execution time, latency metrics, and any time interval that is precisely defined. Period measures elapsed time in calendar units (days, months, years) between two LocalDate values — use it for age calculations, subscription periods, and any interval where calendar semantics matter more than precise milliseconds.
For formatting and parsing, use java.time.format.DateTimeFormatter with the patterns from java.time rather than the legacy java.text formatting classes. DateTimeFormatter is immutable and thread-safe, whereas SimpleDateFormat is neither. The pattern syntax is similar but the new API is more consistent and supports localization properly.
- Use
LocalDatefor birthdays, holidays, and any date without a time component - Use
Instantfor all machine timestamps — log timestamps, metric events, and event sourcing - Use
ZonedDateTimefor scheduled events that must fire at a specific wall-clock time - Use
Durationfor precise elapsed time between instants; usePeriodfor calendar-based intervals - Always use
DateTimeFormatterfromjava.time.format— neverSimpleDateFormatfor new code java.timetypes are immutable — methods likeplusDays()return new instances, they do not mutate
Category
Related Posts
Abstract Classes in Java
Learn about partially implemented classes that define contracts for subclasses using abstract methods and concrete implementations.
Arithmetic Operators in Java
Master Java arithmetic operators: addition, subtraction, multiplication, division, and modulo with integer division gotchas and operator precedence explained.
Array Basics in Java
Learn Java array fundamentals: declaration, initialization, element access, and the length property explained simply.