Skip to content
Java java8 7 min read

Date/Time API (java.time)

Working with dates and times is a common task in almost every application, but Java’s original Date and Calendar classes were clunky, mutable, and notoriously hard to use correctly. Java 8 introduced the java.time package — a clean, immutable, thread-safe API inspired by the Joda-Time library that finally makes date/time work feel natural.

Why the Old API Was Broken

Before Java 8, you had java.util.Date and java.util.Calendar. They had serious problems:

  • Date was mutable — any method could silently change it.
  • Months were zero-indexed in Calendar (Calendar.JANUARY == 0), causing endless bugs.
  • Neither class was thread-safe.
  • Time zones were painful to deal with.
  • No concept of “just a date” vs “just a time” vs “a date with time”.

Note: java.util.Date and Calendar still exist for backward compatibility, but you should use java.time for all new code.

Core Classes at a Glance

The java.time package splits the concept of “date/time” into focused, single-purpose classes:

ClassWhat it represents
LocalDateA date (year, month, day) — no time, no time zone
LocalTimeA time of day — no date, no time zone
LocalDateTimeDate + time — no time zone
ZonedDateTimeDate + time + time zone
InstantA point in time (UTC epoch-based timestamp)
DurationAmount of time in seconds/nanoseconds
PeriodAmount of time in years/months/days
ZoneIdA time zone identifier (e.g., "America/New_York")
DateTimeFormatterParsing and formatting date/time values

All these classes are immutable. Operations like plusDays() return a new object — they never modify the original.

LocalDate

LocalDate represents a calendar date with no time component — perfect for birthdays, deadlines, and calendar events.

import java.time.LocalDate;
import java.time.Month;

public class LocalDateDemo {
    public static void main(String[] args) {
        LocalDate today = LocalDate.now();
        LocalDate christmas = LocalDate.of(2025, Month.DECEMBER, 25);
        LocalDate fromString = LocalDate.parse("2025-07-04");

        System.out.println("Today: " + today);
        System.out.println("Christmas: " + christmas);
        System.out.println("Independence Day: " + fromString);
        System.out.println("Day of week: " + today.getDayOfWeek());
        System.out.println("Is leap year: " + today.isLeapYear());

        LocalDate nextWeek = today.plusWeeks(1);
        System.out.println("Next week: " + nextWeek);
    }
}

Output:

Today: 2026-06-13
Christmas: 2025-12-25
Independence Day: 2025-07-04
Day of week: SATURDAY
Is leap year: false
Next week: 2026-06-20

LocalTime

LocalTime represents a time of day, without any date or time zone.

import java.time.LocalTime;

public class LocalTimeDemo {
    public static void main(String[] args) {
        LocalTime now = LocalTime.now();
        LocalTime meeting = LocalTime.of(14, 30, 0); // 2:30 PM

        System.out.println("Current time: " + now);
        System.out.println("Meeting at: " + meeting);
        System.out.println("Hour: " + now.getHour());
        System.out.println("Is before meeting: " + now.isBefore(meeting));

        LocalTime oneHourLater = meeting.plusHours(1);
        System.out.println("Meeting ends: " + oneHourLater);
    }
}

Output:

Current time: 09:15:42.123
Meeting at: 14:30
Hour: 9
Is before meeting: true
Meeting ends: 15:30

LocalDateTime

LocalDateTime combines date and time — useful when you need both but don’t care about time zones (like logging events in the same region).

import java.time.LocalDateTime;
import java.time.Month;

public class LocalDateTimeDemo {
    public static void main(String[] args) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime event = LocalDateTime.of(2025, Month.DECEMBER, 31, 23, 59, 59);

        System.out.println("Now: " + now);
        System.out.println("New Year's Eve: " + event);
        System.out.println("Year: " + now.getYear());
        System.out.println("Month: " + now.getMonth());
    }
}

Output:

Now: 2026-06-13T09:15:42.123
New Year's Eve: 2025-12-31T23:59:59
Year: 2026
Month: JUNE

ZonedDateTime and ZoneId

When you need true time zone awareness — for international apps, scheduling, or storing timestamps — use ZonedDateTime.

import java.time.ZonedDateTime;
import java.time.ZoneId;

public class ZonedDemo {
    public static void main(String[] args) {
        ZonedDateTime nowInUTC = ZonedDateTime.now(ZoneId.of("UTC"));
        ZonedDateTime nowInNY = ZonedDateTime.now(ZoneId.of("America/New_York"));
        ZonedDateTime nowInTokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));

        System.out.println("UTC:       " + nowInUTC);
        System.out.println("New York:  " + nowInNY);
        System.out.println("Tokyo:     " + nowInTokyo);

        // Convert between zones
        ZonedDateTime tokyoTime = nowInNY.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
        System.out.println("NY time in Tokyo: " + tokyoTime);
    }
}

Tip: Use ZoneId.getAvailableZoneIds() to get a full list of valid zone IDs. Always prefer IANA zone names like "Europe/London" over short abbreviations like "EST", which can be ambiguous.

Instant

Instant is the machine-friendly time representation — a point on the UTC timeline measured in seconds and nanoseconds from the Unix epoch (1970-01-01T00:00:00Z). It’s ideal for storing timestamps in databases.

import java.time.Instant;
import java.time.temporal.ChronoUnit;

public class InstantDemo {
    public static void main(String[] args) {
        Instant now = Instant.now();
        Instant future = now.plus(5, ChronoUnit.HOURS);

        System.out.println("Now (epoch seconds): " + now.getEpochSecond());
        System.out.println("Now: " + now);
        System.out.println("5 hours later: " + future);
        System.out.println("Is before future: " + now.isBefore(future));
    }
}

Output:

Now (epoch seconds): 1749811200
Now: 2026-06-13T09:00:00Z
5 hours later: 2026-06-13T14:00:00Z
Is before future: true

Duration and Period

Duration measures time in hours, minutes, seconds, and nanoseconds. Period measures time in years, months, and days.

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Period;

public class DurationPeriodDemo {
    public static void main(String[] args) {
        // Duration — for time-based amounts
        LocalTime start = LocalTime.of(9, 0);
        LocalTime end = LocalTime.of(17, 30);
        Duration workDay = Duration.between(start, end);
        System.out.println("Work day: " + workDay.toHours() + " hours " + workDay.toMinutesPart() + " minutes");

        // Period — for date-based amounts
        LocalDate birthday = LocalDate.of(1995, 8, 15);
        LocalDate today = LocalDate.of(2026, 6, 13);
        Period age = Period.between(birthday, today);
        System.out.println("Age: " + age.getYears() + " years, " + age.getMonths() + " months");

        // Explicit creation
        Duration twoHours = Duration.ofHours(2);
        Period threeMonths = Period.ofMonths(3);
        System.out.println("Two hours in seconds: " + twoHours.getSeconds());
        System.out.println("Three months: " + threeMonths);
    }
}

Output:

Work day: 8 hours 30 minutes
Age: 30 years, 9 months
Two hours in seconds: 7200
Three months: P3M

Note: toMinutesPart() was added in Java 9. On Java 8, use workDay.toMinutes() % 60 instead.

DateTimeFormatter

You’ll often need to parse date strings from user input or external systems, and format dates for display. DateTimeFormatter handles both.

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class FormatterDemo {
    public static void main(String[] args) {
        // Predefined formatters
        LocalDate date = LocalDate.now();
        System.out.println(date.format(DateTimeFormatter.ISO_LOCAL_DATE));

        // Custom pattern
        DateTimeFormatter custom = DateTimeFormatter.ofPattern("dd MMM yyyy");
        System.out.println(date.format(custom));

        // Parsing a string
        LocalDate parsed = LocalDate.parse("25/12/2025", DateTimeFormatter.ofPattern("dd/MM/yyyy"));
        System.out.println("Parsed: " + parsed);

        // Format LocalDateTime
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter dtFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        System.out.println("Formatted: " + now.format(dtFormatter));
    }
}

Output:

2026-06-13
13 Jun 2026
Parsed: 2025-12-25
Formatted: 2026-06-13 09:15:42

Common pattern letters:

SymbolMeaningExample
yyyy4-digit year2026
MM2-digit month06
MMMShort month nameJun
dd2-digit day13
HHHour (24h)14
hhHour (12h)02
mmMinutes30
ssSeconds45
aAM/PMPM
zTime zone nameUTC

Comparing and Querying Dates

import java.time.LocalDate;

public class CompareDemo {
    public static void main(String[] args) {
        LocalDate date1 = LocalDate.of(2025, 1, 15);
        LocalDate date2 = LocalDate.of(2025, 6, 20);

        System.out.println("isBefore: " + date1.isBefore(date2));   // true
        System.out.println("isAfter:  " + date1.isAfter(date2));    // false
        System.out.println("isEqual:  " + date1.isEqual(date2));    // false

        // Days between two dates
        long daysBetween = date1.until(date2, java.time.temporal.ChronoUnit.DAYS);
        System.out.println("Days between: " + daysBetween);
    }
}

Output:

isBefore: true
isAfter:  false
isEqual:  false
Days between: 156

Interoperability with Legacy Code

You may need to convert between java.time and the old java.util.Date when working with legacy APIs or JDBC drivers.

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;

public class LegacyConversionDemo {
    public static void main(String[] args) {
        // java.util.Date → Instant
        Date legacyDate = new Date();
        Instant instant = legacyDate.toInstant();
        System.out.println("Instant: " + instant);

        // Instant → LocalDateTime
        LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
        System.out.println("LocalDateTime: " + ldt);

        // LocalDateTime → java.util.Date
        Date backToLegacy = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
        System.out.println("Back to Date: " + backToLegacy);
    }
}

Tip: When using JDBC with modern drivers (JDBC 4.2+), you can pass LocalDate and LocalDateTime directly via PreparedStatement.setObject() — no conversion needed.

Under the Hood

Immutability and thread safety. Every java.time class is declared final and stores all fields as private final. This means instances can be freely shared across threads without synchronization — a huge advantage over Calendar.

Value-based semantics. LocalDate, LocalTime, and friends are conceptually value types (similar to how int works). You should never use == to compare them — always use .equals() or the isBefore()/isAfter() methods. Java 21’s records and future value types (Project Valhalla) follow the same philosophy.

Instant vs LocalDateTime storage. An Instant is stored as two long fields: epochSecond and nanos. A LocalDateTime is stored as a LocalDate (packed into one long as year/month/day) and a LocalTime (stored as nanoseconds since midnight). Neither stores a time zone, which is why converting between them always requires a ZoneId.

Temporal arithmetic. Methods like plusDays() and minusMonths() go through the Temporal interface and TemporalUnit/TemporalAmount abstractions. This design lets ChronoUnit (e.g., ChronoUnit.WEEKS) and Period both plug in — a textbook use of interfaces.

Formatting performance. DateTimeFormatter instances are thread-safe and expensive to create. Always store them as static final constants rather than creating them inside loops or methods.

// Good practice
private static final DateTimeFormatter FORMATTER =
    DateTimeFormatter.ofPattern("dd/MM/yyyy");
  • Lambda Expressionsjava.time APIs work beautifully with lambdas and streams for filtering and transforming dates
  • Stream API — combine with streams to process collections of dates efficiently
  • Optional — date parsing can fail; Optional is the modern way to handle absent values safely
  • Formatting Dates & Times — locale-aware formatting for internationalized applications
  • Java 8 Features — the full picture of what arrived in Java 8 alongside java.time
  • Records — immutable data classes, sharing the same design philosophy as java.time
Last updated June 13, 2026
Was this helpful?