skip to content

How do you convert between legacy java.util.Date and the modern java.time API?

level: middleimportance: must knowfreq 60%

answer

  1. Bridge is Instant: date.toInstant() / Date.from(instant)
  2. Date & Instant = absolute; Local* = zoneless
  3. Crossing families needs a ZoneId
  4. instant.atZone(zone) -> ZonedDateTime -> toLocalDate
  5. java.sql types: toLocalDate / toInstant

basics

~20 s

Use date.toInstant() to turn a Date into an Instant, and Date.from(instant) to go back. To get a LocalDateTime you must add a time zone: date.toInstant().atZone(zoneId). To convert a LocalDate you choose a zone and take an Instant from it.

solid answer

~40 s

java.util.Date and java.time.Instant both represent the same thing — a point on the timeline in UTC — so the bridge is Instant. Going legacy to modern: date.toInstant() gives an Instant; from there instant.atZone(ZoneId.systemDefault()) (or a specific zone) gives a ZonedDateTime, and .toLocalDate()/.toLocalDateTime() drop the zone. Going modern to legacy: Date.from(instant). For LocalDateTime, which has no zone, you must supply one to pin it to an instant: Date.from(localDateTime.atZone(zoneId).toInstant()). The key insight is that LocalDate/LocalDateTime are zone-less, while Date and Instant are absolute instants, so any conversion between the two families requires choosing a ZoneId. Note java.sql.Date/Timestamp also have toLocalDate()/toInstant() helpers for JDBC interop. Avoid the old Date.getYear()-style accessors entirely.

code

java · 15 lines
java
// Legacy Date -> modern java.time
Date legacy = new Date();
Instant instant = legacy.toInstant();
ZonedDateTime zdt = instant.atZone(ZoneId.systemDefault());
LocalDate date = zdt.toLocalDate();

// Modern java.time -> legacy Date
LocalDateTime ldt = LocalDateTime.of(2026, 6, 20, 14, 30);
Date back = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());

// LocalDate needs a zone + a time-of-day to become an instant
Date startOfDay = Date.from(
        LocalDate.of(2026, 6, 20)
                 .atStartOfDay(ZoneId.of("Europe/Paris"))
                 .toInstant());

go deeper

for a junior

Knows the two helper calls: date.toInstant() and Date.from(instant).

for a middle

Performs full round-trips including LocalDate/LocalDateTime, correctly supplying a ZoneId where required.

for a senior

Explains the absolute-vs-local distinction driving when a zone is needed, handles JDBC/SQL interop, and avoids zone-ambiguity bugs.

for a principal

Sets boundary conventions (store Instant/UTC, convert only at edges), audits a legacy codebase's conversions, and reasons about DST and cross-zone correctness.

## The core idea: instants vs. local types To convert correctly you must understand two families: - **Absolute / machine types** — `java.util.Date` and `java.time.Instant`. Both represent **a single point on the global timeline**, measured from the 1970 UTC epoch. They carry no human time zone; they are just "this exact moment everywhere." - **Local / human types** — `LocalDate`, `LocalTime`, `LocalDateTime`. These describe a calendar value **with no time zone** — "2026-06-20 at 14:30" without saying *where*. The same wall-clock value is a different instant in Tokyo than in New York. Because of this, **any conversion that crosses the two families must involve a `ZoneId`** (a time zone identifier such as `ZoneId.of("Europe/Paris")` or `ZoneId.systemDefault()`). Conversions that stay within the absolute family do not. ## Legacy Date -> modern ```java Date legacy = new Date(); Instant instant = legacy.toInstant(); // same instant, no zone needed ZonedDateTime zdt = instant.atZone(ZoneId.systemDefault()); // pin to a zone LocalDateTime ldt = zdt.toLocalDateTime(); // drop the zone -> wall clock LocalDate ld = zdt.toLocalDate(); // just the date part ``` `Date.toInstant()` (added in Java 8) is the clean entry point. From the `Instant` you attach a zone to get a `ZonedDateTime`, then peel off whatever local part you want. ## Modern -> legacy Date ```java Instant instant = Instant.now(); Date d1 = Date.from(instant); // straightforward LocalDateTime ldt = LocalDateTime.of(2026, 6, 20, 14, 30); Date d2 = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()); // must add a zone LocalDate ld = LocalDate.of(2026, 6, 20); Date d3 = Date.from(ld.atStartOfDay(ZoneId.systemDefault()).toInstant()); // date -> start of day -> instant ``` `Date.from(Instant)` is the reverse of `toInstant()`. For a `LocalDateTime` or `LocalDate` you must first **resolve it to an instant** by choosing a zone (`atZone`, `atStartOfDay`). ## A common bug Do **not** assume a `LocalDate` maps to a single `Date` — it depends on the zone. `LocalDate.of(2026,6,20).atStartOfDay(ZoneId.of("Asia/Tokyo"))` and `...atStartOfDay(ZoneId.of("America/New_York"))` produce different instants (and possibly even different calendar dates once you convert back through another zone). Always be explicit about the zone, and prefer storing/transmitting **`Instant`/UTC** for absolute timestamps. ## JDBC / SQL interop The `java.sql` types bridge too: `java.sql.Date.toLocalDate()`, `java.sql.Timestamp.toInstant()` / `toLocalDateTime()`, and the static `valueOf(...)` factories convert back. Modern JDBC drivers (and JPA 2.2+) also accept `java.time` types directly, so you can often skip the legacy hop entirely. ## Don't use the old accessors Never reach back to `Date.getYear()/getMonth()/getHours()` for conversion — they are deprecated, use the 1900-offset/0-based conventions, and silently apply the default zone. Always go through `toInstant()`.

  • Why does converting a LocalDateTime to a Date require a ZoneId, but converting an Instant does not?
    An Instant is already an absolute point on the timeline (UTC), matching what Date stores, so no zone is needed. A LocalDateTime is wall-clock time with no zone, so it maps to different instants in different zones — you must pick one to resolve it.
  • If you only care about an absolute timestamp, what type should you store?
    Instant (or store epoch millis / a UTC timestamp). It is zone-independent and converts cleanly to and from Date and database timestamp columns.

saying these in an interview costs you the question

  • Converting LocalDate/LocalDateTime to Date without choosing a zone
  • Using deprecated Date.getYear()/getMonth() to read fields
  • Assuming LocalDate maps to one fixed Date regardless of zone
  • Thinking Instant carries a time zone

context