Java 8 – New Date and Time API (java.time)
Java's old java.util.Date and Calendar classes are widely regarded as poorly designed — they are mutable, not thread-safe, months are 0-indexed, and the API is confusing. Java 8 introduced the java.time package (inspired by Joda-Time) with immutable, thread-safe, and clearly named classes. This tutorial covers all the essential classes with practical examples.
Key Classes in java.time
| Class | Represents | Has Time Zone? |
|---|---|---|
LocalDate |
Date only (year, month, day) | No |
LocalTime |
Time only (hour, minute, second, nano) | No |
LocalDateTime |
Date + Time (no zone) | No |
ZonedDateTime |
Date + Time + Zone | Yes |
Instant |
Machine timestamp (nanoseconds since epoch) | No (always UTC) |
Duration |
Amount of time (hours, minutes, seconds) | No |
Period |
Amount of time in date units (years, months, days) | No |
DateTimeFormatter |
Format and parse date/time strings | No |
LocalDate – Working with Dates
import java.time.*;
import java.time.temporal.ChronoUnit;
// Create
LocalDate today = LocalDate.now();
LocalDate specific = LocalDate.of(2026, 6, 15); // June 15, 2026
LocalDate fromStr = LocalDate.parse("2026-06-15"); // ISO format
System.out.println("Today: " + today);
System.out.println("Specific: " + specific);
// Access fields
System.out.println("Year: " + specific.getYear()); // 2026
System.out.println("Month: " + specific.getMonth()); // JUNE
System.out.println("MonthNo: " + specific.getMonthValue()); // 6
System.out.println("Day: " + specific.getDayOfMonth()); // 15
System.out.println("DayOfWk: " + specific.getDayOfWeek()); // MONDAY
// Arithmetic — all return new instances (immutable)
LocalDate tomorrow = today.plusDays(1);
LocalDate nextMonth = today.plusMonths(1);
LocalDate lastYear = today.minusYears(1);
// Comparison
System.out.println(specific.isAfter(today)); // depends on today
System.out.println(specific.isBefore(LocalDate.of(2027, 1, 1))); // true
// Days between two dates
long daysBetween = ChronoUnit.DAYS.between(today, LocalDate.of(2027, 1, 1));
System.out.println("Days until 2027: " + daysBetween);
// Check properties
System.out.println("Leap year? " + today.isLeapYear());
System.out.println("Days in month: " + today.lengthOfMonth());
LocalTime – Working with Time
LocalTime now = LocalTime.now();
LocalTime meeting = LocalTime.of(14, 30, 0); // 14:30:00
LocalTime parsed = LocalTime.parse("09:15:30");
System.out.println("Now: " + now);
System.out.println("Meeting: " + meeting);
// Access fields
System.out.println("Hour: " + meeting.getHour()); // 14
System.out.println("Minute: " + meeting.getMinute()); // 30
System.out.println("Second: " + meeting.getSecond()); // 0
// Arithmetic
LocalTime oneHourLater = meeting.plusHours(1); // 15:30
LocalTime tenMinsEarly = meeting.minusMinutes(10); // 14:20
// Is it AM or PM?
System.out.println("AM? " + now.isBefore(LocalTime.NOON));
LocalDateTime – Date + Time Together
LocalDateTime now = LocalDateTime.now();
LocalDateTime dt = LocalDateTime.of(2026, 6, 15, 10, 30, 0);
LocalDateTime dtFromParts = LocalDate.of(2026, 6, 15)
.atTime(LocalTime.of(10, 30));
System.out.println("Now: " + now);
System.out.println("DT: " + dt);
// Extract parts
LocalDate datePart = dt.toLocalDate();
LocalTime timePart = dt.toLocalTime();
// Arithmetic
LocalDateTime twoHoursLater = dt.plusHours(2); // 2026-06-15T12:30
LocalDateTime nextWeek = dt.plusWeeks(1);
// Duration between two LocalDateTimes
Duration duration = Duration.between(dt, dt.plusHours(3).plusMinutes(45));
System.out.println("Duration: " + duration.toHours() + "h " +
duration.toMinutesPart() + "m"); // 3h 45m
ZonedDateTime – Time with Time Zone
// Current time in India
ZonedDateTime india = ZonedDateTime.now(ZoneId.of("Asia/Kolkata"));
System.out.println("India: " + india);
// Current time in New York
ZonedDateTime newYork = ZonedDateTime.now(ZoneId.of("America/New_York"));
System.out.println("New York: " + newYork);
// Convert between zones
ZonedDateTime indiaInLondon = india.withZoneSameInstant(ZoneId.of("Europe/London"));
System.out.println("India time in London: " + indiaInLondon);
// List all available zone IDs
ZoneId.getAvailableZoneIds().stream()
.filter(z -> z.startsWith("Asia"))
.sorted()
.forEach(System.out::println);
DateTimeFormatter – Format and Parse
import java.time.format.DateTimeFormatter;
LocalDateTime dt = LocalDateTime.of(2026, 6, 15, 14, 30, 0);
// Built-in formatters
System.out.println(dt.format(DateTimeFormatter.ISO_LOCAL_DATE)); // 2026-06-15
System.out.println(dt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); // 2026-06-15T14:30:00
// Custom pattern
DateTimeFormatter custom = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
System.out.println(dt.format(custom)); // 15/06/2026 14:30:00
DateTimeFormatter indian = DateTimeFormatter.ofPattern("d MMMM yyyy, h:mm a");
System.out.println(dt.format(indian)); // 15 June 2026, 2:30 PM
// Parsing — string → LocalDateTime
LocalDateTime parsed = LocalDateTime.parse("15/06/2026 14:30:00", custom);
System.out.println(parsed); // 2026-06-15T14:30
// Parse with locale
DateTimeFormatter withLocale = DateTimeFormatter.ofPattern("d MMMM yyyy",
java.util.Locale.ENGLISH);
LocalDate date = LocalDate.parse("15 June 2026", withLocale);
System.out.println(date); // 2026-06-15
Period and Duration
// Period — date-based amount (years, months, days)
LocalDate start = LocalDate.of(1990, 1, 15);
LocalDate end = LocalDate.now();
Period age = Period.between(start, end);
System.out.printf("Age: %d years, %d months, %d days%n",
age.getYears(), age.getMonths(), age.getDays());
Period tenDays = Period.ofDays(10);
Period twoYears = Period.ofYears(2);
LocalDate future = end.plus(twoYears);
// Duration — time-based amount (hours, minutes, seconds, nanos)
Duration twoHours = Duration.ofHours(2);
Duration ninetyMin = Duration.ofMinutes(90);
System.out.println("2h in minutes: " + twoHours.toMinutes()); // 120
System.out.println("90min in hours: " + ninetyMin.toHours()); // 1
// Duration between two instants
Instant before = Instant.now();
Thread.sleep(100);
Instant after = Instant.now();
Duration elapsed = Duration.between(before, after);
System.out.println("Elapsed ms: " + elapsed.toMillis()); // ~100
Instant – Machine Timestamp
Instant now = Instant.now();
System.out.println("Epoch seconds: " + now.getEpochSecond());
System.out.println("Instant: " + now); // 2026-06-15T09:00:00.123456789Z
// Convert to/from LocalDateTime
LocalDateTime ldt = LocalDateTime.ofInstant(now, ZoneId.systemDefault());
Instant back = ldt.toInstant(java.time.ZoneOffset.UTC);
// Legacy interop — converting old Date to new API
java.util.Date oldDate = new java.util.Date();
Instant fromOld = oldDate.toInstant();
LocalDateTime fromOldLdt = LocalDateTime.ofInstant(fromOld, ZoneId.systemDefault());
Old API vs New API – Comparison
| Old (java.util) | New (java.time) | Improvement |
|---|---|---|
new Date() |
LocalDate.now() |
Immutable, clearly named |
Calendar.JANUARY = 0 |
Month.JANUARY = 1 |
No more 0-indexed months |
SimpleDateFormat (not thread-safe) |
DateTimeFormatter (thread-safe) |
Safe to share across threads |
| Mutable — date.setTime() | All operations return new objects | No accidental shared-state bugs |
| No timezone concept in Date | ZonedDateTime explicit |
Clear timezone handling |
Summary
Java 8's java.time package replaces every class from java.util.Date and Calendar. Use LocalDate for dates without time, LocalDateTime for combined date-time without a zone, and ZonedDateTime when the timezone matters (storing events, scheduling). DateTimeFormatter handles all formatting and parsing and is thread-safe. Period measures calendar-unit gaps (years/months/days); Duration measures time-unit gaps (hours/minutes/seconds). All classes are immutable — every arithmetic operation returns a new object, making the API safe for multi-threaded use.
Comments