How do you convert between epoch milliseconds (a long) and Instant, and what are the precision pitfalls?
answer
- Instant.ofEpochMilli(long) / instant.toEpochMilli()
- Epoch = 1970-01-01T00:00:00Z, UTC, zone-independent
- Instant has nanos; millis truncates sub-ms
- toEpochMilli can throw ArithmeticException (overflow)
- Use ofEpochSecond(sec, nano) for lossless
basics
~10 sUse Instant.ofEpochMilli(long) to go from milliseconds to an Instant, and instant.toEpochMilli() to go back. The long counts milliseconds since 1970-01-01 UTC. toEpochMilli() drops sub-millisecond nanos.
solid answer
~30 sEpoch milliseconds is a long counting milliseconds since the Unix epoch (1970-01-01T00:00:00Z). Instant.ofEpochMilli(millis) builds an Instant from it, and instant.toEpochMilli() returns it. The key pitfall is precision: Instant stores nanosecond resolution, but toEpochMilli() truncates anything finer than a millisecond, so a round-trip through a long is lossy if the Instant carried micro/nanoseconds (e.g. from a high-resolution clock). There's also a documented overflow: toEpochMilli() throws ArithmeticException for Instants far outside the long-millis range. For sub-millisecond fidelity use ofEpochSecond(seconds, nanoAdjustment) and getEpochSecond()/getNano(), or pass the Instant around directly instead of a long.
code
java · 6 lineslong millis = System.currentTimeMillis();
Instant instant = Instant.ofEpochMilli(millis);
long back = instant.toEpochMilli();
// Lossless path (keeps nanoseconds):
Instant exact = Instant.ofEpochSecond(instant.getEpochSecond(), instant.getNano());go deeper
Can call Instant.ofEpochMilli and toEpochMilli and knows epoch millis is time since 1970 UTC.
Explains the millisecond truncation, that Instant stores nanos, and uses ofEpochSecond(sec,nano) when precision matters.
Knows the ArithmeticException overflow, that JDK 9+ clocks can yield sub-ms precision, and designs serialization to avoid lossy round-trips.
Sets organization-wide conventions (store Instant/ISO-8601 vs epoch millis), reasons about precision/interop tradeoffs across services and DBs, and audits for unit-mismatch bugs.
## What "epoch millis" means The **Unix epoch** is the fixed reference instant **1970-01-01T00:00:00Z** (Z = UTC). "Epoch milliseconds" is simply a `long` counting how many milliseconds have elapsed since that instant (negative for earlier times). It is the lingua franca of timestamps: `System.currentTimeMillis()`, JSON APIs, databases, and JavaScript's `Date` all use it. Because it is measured from a UTC reference, an epoch-millis value is **zone-independent** — it identifies one global moment, exactly like an `Instant`. ## The conversions ```java long millis = 1_750_000_000_000L; // long -> Instant Instant instant = Instant.ofEpochMilli(millis); // Instant -> long long back = instant.toEpochMilli(); ``` That's the whole happy path. `ofEpochMilli` splits the long into whole seconds plus a millisecond remainder internally; `toEpochMilli` reassembles them. ## Pitfall 1: precision loss An `Instant` internally stores **two fields**: `epochSecond` (a long) and `nano` (0–999,999,999), giving it **nanosecond** resolution. Milliseconds only have 3 decimal places of a second, so: - `toEpochMilli()` **truncates** anything below a millisecond. An Instant of `...:00.123456789Z` becomes `...123` millis — the `456789` nanos are silently lost. - Round-tripping `Instant -> long -> Instant` therefore **loses sub-millisecond data**. On JDK 9+, `Instant.now()` can return microsecond/nanosecond precision (depending on the OS clock), so this loss is real, not theoretical. To keep full precision, avoid the millis hop entirely — pass the `Instant` itself — or use the second+nano API: ```java long secs = instant.getEpochSecond(); int nanos = instant.getNano(); Instant exact = Instant.ofEpochSecond(secs, nanos); // lossless ``` ## Pitfall 2: overflow `Instant` can represent dates billions of years out, far beyond what a long of *milliseconds* can hold. `toEpochMilli()` throws **`ArithmeticException`** if the instant is outside the representable millis range. `ofEpochMilli` never overflows (a long of millis always fits an Instant). ## Pitfall 3: seconds vs millis confusion A frequent bug is mixing `ofEpochSecond` and `ofEpochMilli`. `Instant.ofEpochSecond(1_750_000_000_000L)` interprets the value as **seconds**, putting you ~55,000 years in the future. Always match the unit of your source data. ## Mental model Epoch millis and `Instant` are the *same idea* (a UTC moment) at *different precisions*: millis is a coarse long; Instant is a precise object. Convert freely, but remember the long is the lower-resolution container — going Instant→long→Instant can quietly round you off.
- How would you preserve nanosecond precision when serializing an Instant?Either pass/serialize getEpochSecond() and getNano() (or an ISO-8601 string, which keeps nanos), or store the Instant directly; avoid the toEpochMilli() round-trip, which truncates to milliseconds.
- Why doesn't epoch millis need a time zone?It counts from a fixed UTC reference (the epoch), so the same long is the same global moment everywhere; a zone is only needed to render it as a wall-clock LocalDateTime.
saying these in an interview costs you the question
- Assuming Instant->millis->Instant is always lossless
- Confusing ofEpochSecond with ofEpochMilli
- Thinking epoch millis carries a time zone
- Believing toEpochMilli can never throw