W3docs

Java Instant

Represent a moment on the timeline in UTC in Java with Instant — useful for timestamps and machine time.

Java Instant

Instant is a single moment on the global timeline, stored as nanoseconds since the Unix epoch (1970-01-01T00:00:00Z). It has no zone, no calendar, no notion of "what day is it" — just a count. That count is in UTC by construction, so two Instants from different machines in different zones compare directly: the one with the lower number is earlier.

This is the type for machine timestamps. Log lines. Message timestamps. "When did the server receive the request." Audit trails. Anything that needs to be sortable globally and that should never be ambiguous about which day it represents — because there is no "day" at all, only seconds.

Creating

Instant now    = Instant.now();                              // current moment, from System clock
Instant epoch  = Instant.EPOCH;                              // 1970-01-01T00:00:00Z
Instant max    = Instant.MAX;                                 // year +1000000000
Instant min    = Instant.MIN;                                 // year -1000000000

Instant fromS   = Instant.ofEpochSecond(1_700_000_000L);
Instant fromMs  = Instant.ofEpochMilli(1_700_000_000_000L);
Instant fromIso = Instant.parse("2025-11-04T19:30:00Z");     // ISO-8601, trailing Z is mandatory

The string form ends in a literal Z (for "Zulu time," military for UTC). Instant.parse("2025-11-04T19:30:00") (no Z) is a parse error — the type refuses to guess what zone you meant.

Two factories you'll use often:

Instant.ofEpochSecond(epochSec);                              // long seconds, no nanos
Instant.ofEpochSecond(epochSec, nanos);                       // with sub-second resolution

Most external timestamp formats (Unix time(2), syslog, JSON created_at integers) are seconds or milliseconds since the epoch. The ofEpochSecond / ofEpochMilli factories are the standard bridge.

Resolution

Instant is nanosecond precision (1 second = 1,000,000,000 ns). On most systems the underlying clock has lower resolution — millisecond is typical, microsecond on modern Linux. Instant.now() returns a value at the available resolution; the unused nanoseconds are zero.

Accessors:

long seconds = inst.getEpochSecond();                         // long; can go past 2038
int nanos    = inst.getNano();                                // 0-999_999_999
long milli   = inst.toEpochMilli();                           // throws if out of long range

toEpochMilli is the lossy conversion: nanos get truncated to millis. For log lines and JSON timestamps this is usually fine; for high-frequency event records, use getEpochSecond + getNano separately.

No calendar

Instant.getDayOfMonth() doesn't exist. Neither does getYear, getHour, or any of the calendar accessors. The type genuinely doesn't know — calendar information requires a zone, and Instant doesn't have one. If you want to ask "what hour was it in New York when this happened," you have to attach a zone first:

ZonedDateTime zdt = inst.atZone(ZoneId.of("America/New_York"));
int hour = zdt.getHour();
LocalDate date = zdt.toLocalDate();

atZone(zone) is the bridge in the other direction from ZonedDateTime.toInstant(). The two together give you the full round-trip: moment ↔ zoned label.

Arithmetic

Same fluent shape:

inst.plusSeconds(60);
inst.plusMillis(500);
inst.plusNanos(1_000_000);
inst.plus(Duration.ofMinutes(15));                            // any Duration

inst.minus(Duration.ofDays(1));                                // exactly 24h * 3600s

No plusDays on Instant (in the calendar sense). It has plus(amount, ChronoUnit), and ChronoUnit.DAYS works because the JDK defines a Day as exactly 24 hours of seconds for Instant. That's not what a calendar day is when DST is in play, which is the whole reason Instant doesn't pretend to be one.

inst.plus(1, ChronoUnit.DAYS);                                // exactly 86_400 seconds
inst.plus(7, ChronoUnit.DAYS);                                // exactly 604_800 seconds

For calendar-shaped operations ("one month later in the user's zone"), go through ZonedDateTime:

Instant later = inst.atZone(zone).plusMonths(1).toInstant();

Comparing

inst1.isBefore(inst2);
inst1.isAfter(inst2);
inst1.equals(inst2);
inst1.compareTo(inst2);

Instant implements Comparable<Instant> with natural ordering by epoch second then nanos. equals is straightforward: same second and same nanos.

Distance

Duration d = Duration.between(start, end);                     // a Duration
long millis = ChronoUnit.MILLIS.between(start, end);
long days = ChronoUnit.DAYS.between(start, end);               // 24h-equivalent days

For machine timestamps these are all exact — there's no calendar ambiguity. ChronoUnit.MONTHS.between(start, end) on Instants throws because months are not constant-length when measured in seconds: there's no zone, so the calculator has no way to know which month containing those seconds. That's the right failure mode.

java.util.Date bridge

Old code uses java.util.Date. The conversions are direct:

Date legacy = Date.from(inst);                                 // Instant -> Date
Instant back = legacy.toInstant();                              // Date -> Instant

Date is internally a wrapper around an epoch-millisecond long, so the round-trip is lossless modulo nanoseconds (Date is millisecond precision, Instant is nanosecond). The Legacy Date chapter covers the migration in detail.

Why everything internal should be Instant

The recommendation that has emerged from running java.time in production for ten years:

  • Internally, use Instant. Storage, comparison, logging, message timestamps, anywhere the value flows machine-to-machine.
  • At the boundary — when displaying to a user, when accepting user input — convert to ZonedDateTime or LocalDateTime using the right zone for the context.

This separates "what really happened" from "how it's labelled." A bug in the boundary (wrong zone) leaves your internal values correct; a bug in the type system that lets LocalDateTime flow internally tends to leave you with timestamps that are silently in different zones.

A worked example: a tiny event log

The program below records a sequence of events as Instants, computes inter-event durations, demonstrates the calendar boundary by attaching a zone for display, shows the legacy Date bridge, and finally illustrates the "no calendar" rule by showing that ChronoUnit.MONTHS.between on two Instants throws.

java— editable, runs on the server

What to take from the run:

  • Instant.parse(\"2025-11-04T19:30:00Z\") parsed only because of the trailing Z. Drop the Z and the parse fails — the type insists on knowing that the string is UTC. Other zones must go through ZonedDateTime.parse (or be supplied via atZone).
  • The event sequence used Duration.between(...). Every result was a clean integer milliseconds count — no zone confusion, no DST, no calendar arithmetic. That's why server-side timing belongs in Instant: the arithmetic is just subtraction of longs under the hood.
  • The "same instant, two zone labels" block printed ZonedDateTimes that looked different but were the same Instant. atZone(...) is purely a display operation on Instant. If you want to do calendar math (next month, end of week), do it on the ZonedDateTime, then call .toInstant() to come back.
  • Date.from(inst) and legacy.toInstant() were lossless modulo nanoseconds. Date carries only milliseconds, so round-tripping through Date truncates sub-ms precision. For most logging this is fine; for high-precision event capture, stay in Instant end-to-end and never round-trip through Date.
  • ChronoUnit.MONTHS.between(instantA, instantB) threw UnsupportedTemporalTypeException. That's the right failure mode: months are not constant-length seconds, and the JDK refuses to invent an answer. Going through ZonedDateTime supplied the missing zone, and the same call worked. The pattern is general: calendar-shaped operations need a zone, and the type system makes you supply one explicitly.

What's next

Instant is the moment. The next two chapters cover the lengths of time between moments: Java Duration for "X seconds, Y nanos" measurements, and Java Period for "X years, Y months, Z days" calendar lengths. The two together are how you express "an hour later" vs "a month later" without either being lossy.

Practice

Practice

A log line includes `created_at: 1700000000` (a Unix epoch second). You need a `java.time` value you can compare to `Instant.now()` and pass to a `Duration.between(...)` calculation. Which conversion is right?