Instant in Java: the java.time.Instant Class Explained
The line DateTimeFormatter.ofPattern("dd/MM/yyyy").format(Instant.now()) compiles, but at runtime it fails with UnsupportedTemporalTypeException: Unsupported field: DayOfMonth. An Instant has no day, no month and no year — only seconds counted from 1970. To get «15/03/2024», you first have to say in which time zone to look at that moment: .withZone(ZoneId.of("Europe/Stockholm")).
Instant in Java (java.time.Instant) is an immutable, thread-safe class that represents an instant — a single point on the UTC time-line. Internally it holds two numbers: the seconds since the epoch 1970-01-01T00:00:00Z (a long) and the nanoseconds within that second (an int from 0 to 999,999,999). For example, 923,456,789 seconds and 186,054,812 nanoseconds after the epoch is the moment 1999-04-07T03:46:29.186054812Z. The class was added in Java 8, and Instant.now() returns the current moment. Use Instant for timestamps: when a database row was created, when a log event happened, dates exchanged between services.
How to create an Instant: now(), ofEpochSecond(), ofEpochMilli(), parse()
Instant has no public constructor — objects are created with static factory methods. Instant.now() returns the current moment, ofEpochSecond(seconds[, nanos]) and ofEpochMilli(millis) define a moment relative to the epoch 1970-01-01T00:00:00Z, and parse() reads a string such as 2024-03-15T07:30:00Z. Negative values mean moments before 1970.
Here is a Java Instant example with every common way to create one:
import java.time.Instant;
import java.time.temporal.ChronoField;
public class InstantExample1 {
public static void main(String[] args) {
Instant now = Instant.now();
Instant instant = now.with(ChronoField.NANO_OF_SECOND, 10);
// Seconds after the epoch
Instant fiveSecondsAfterEpoch = Instant.ofEpochSecond(5);
// Seconds and nanoseconds (may be negative)
Instant sixSecTwoNanBeforeEpoch = Instant.ofEpochSecond(-6, -2);
// Milliseconds after (or before) the epoch
Instant fiftyMilliSecondsAfterEpoch = Instant.ofEpochMilli(50);
// The example from the definition
Instant y1999 = Instant.ofEpochSecond(923_456_789, 186_054_812);
Instant parsed = Instant.parse("2024-03-15T07:30:00Z");
System.out.println("now: " + now);
System.out.println("instant: " + instant);
System.out.println("fiveSecondsAfterEpoch: " + fiveSecondsAfterEpoch);
System.out.println("sixSecTwoNanBeforeEpoch: " + sixSecTwoNanBeforeEpoch);
System.out.println("fiftyMilliSecondsAfterEpoch: " + fiftyMilliSecondsAfterEpoch);
System.out.println("y1999: " + y1999);
System.out.println("parsed: " + parsed);
System.out.println("EPOCH: " + Instant.EPOCH);
}
} now: 2026-10-19T11:35:07.123456Z (depends on when you run it)
instant: 2026-10-19T11:35:07.000000010Z
fiveSecondsAfterEpoch: 1970-01-01T00:00:05Z
sixSecTwoNanBeforeEpoch: 1969-12-31T23:59:53.999999998Z
fiftyMilliSecondsAfterEpoch: 1970-01-01T00:00:00.050Z
y1999: 1999-04-07T03:46:29.186054812Z
parsed: 2024-03-15T07:30:00Z
EPOCH: 1970-01-01T00:00:00Z Look at the fourth line. ofEpochSecond(-6, -2) means «6 seconds and 2 nanoseconds before the epoch». Inside an Instant the nanosecond part is never negative, so the value is normalised to minus 7 seconds plus 999,999,998 nanoseconds — hence 23:59:53.999999998. toString() prints the fraction of a second in groups of three digits: 50 milliseconds become .050, and 10 nanoseconds become .000000010.
The valid range is from Instant.MIN (-1000000000-01-01T00:00:00Z) to Instant.MAX (+1000000000-12-31T23:59:59.999999999Z). Outside it, ofEpochSecond() throws DateTimeException: Instant exceeds minimum or maximum instant.
Instant.now() in Java: the current moment in UTC
Instant.now() returns the current moment from the system clock in UTC and does not depend on the JVM default time zone: a server in New York and a server in Stockholm get the same value at the same moment. That is why Instant.now() is the standard way to take a timestamp for logs, databases and messages between services.
import java.time.Instant;
import java.time.LocalDateTime;
public class InstantNowExample {
public static void main(String[] args) {
System.out.println("Instant.now(): " + Instant.now());
System.out.println("LocalDateTime.now(): " + LocalDateTime.now());
}
} Instant.now(): 2026-10-19T11:35:07.123456Z
LocalDateTime.now(): 2026-10-19T07:35:07.123512 (JVM zone America/New_York) The Z suffix (Zulu) means UTC, so the Instant is 4 hours «ahead» of New York time. This is not a bug, just another way of writing the same moment. Precision depends on the Java version and the OS: in Java 8 you usually get milliseconds, in Java 9+ typically microseconds. For tests there is the overload Instant.now(Clock) — see the fixed-clock example in now(ZoneId) and now(Clock).
Getting seconds, milliseconds and nanoseconds
An Instant exposes only values tied to the epoch: getEpochSecond() returns the seconds (long), getNano() the nanoseconds within the second (int), and toEpochMilli() the milliseconds since the epoch. The generic get() and getLong() support the fields NANO_OF_SECOND, MICRO_OF_SECOND and MILLI_OF_SECOND; getLong() also supports INSTANT_SECONDS.
import java.time.Instant;
import java.time.temporal.ChronoField;
public class InstantExample2 {
public static void main(String[] args) {
Instant instant = Instant.parse("2024-03-15T10:30:45.123456789Z");
long seconds = instant.getEpochSecond(); // seconds
int nanos1 = instant.getNano(); // nanoseconds
// Value as int
int millis = instant.get(ChronoField.MILLI_OF_SECOND);
int micros = instant.get(ChronoField.MICRO_OF_SECOND);
// Value as long
long nanos2 = instant.getLong(ChronoField.NANO_OF_SECOND);
long epochMillis = instant.toEpochMilli();
System.out.println("Seconds: " + seconds);
System.out.println("Nanoseconds: " + nanos1);
System.out.println("Milliseconds: " + millis);
System.out.println("Microseconds: " + micros);
System.out.println("Nanoseconds: " + nanos2);
System.out.println("Milliseconds since epoch: " + epochMillis);
}
} Seconds: 1710498645
Nanoseconds: 123456789
Milliseconds: 123
Microseconds: 123456
Nanoseconds: 123456789
Milliseconds since epoch: 1710498645123 Note that MILLI_OF_SECOND is the milliseconds within the current second (123), not since the epoch. The milliseconds since the epoch come from toEpochMilli(), which drops any nanoseconds beyond the millisecond. An Instant has no date or time-of-day fields: instant.get(ChronoField.HOUR_OF_DAY) throws UnsupportedTemporalTypeException: Unsupported field: HourOfDay. To find out the hour, day or year, move the moment into a time zone — see Converting Instant.
Comparing Instants: isAfter(), isBefore(), equals()
Two Instant objects are compared with isAfter(), isBefore() and equals(), which return a boolean. For sorting there is compareTo(): the class implements Comparable<Instant>. Seconds are compared first, then nanoseconds; a time zone plays no part because an Instant has none.
import java.time.Instant;
public class InstantExample3 {
public static void main(String[] args) {
Instant now = Instant.now();
Instant fiveSecondsAfterEpoch = Instant.ofEpochSecond(5);
System.out.println("now.isAfter(fiveSecondsAfterEpoch)? " + now.isAfter(fiveSecondsAfterEpoch));
System.out.println("now.isBefore(fiveSecondsAfterEpoch)? " + now.isBefore(fiveSecondsAfterEpoch));
System.out.println("now.equals(fiveSecondsAfterEpoch)? " + now.equals(fiveSecondsAfterEpoch));
System.out.println("now.compareTo(fiveSecondsAfterEpoch) > 0? " + (now.compareTo(fiveSecondsAfterEpoch) > 0));
}
} now.isAfter(fiveSecondsAfterEpoch)? true
now.isBefore(fiveSecondsAfterEpoch)? false
now.equals(fiveSecondsAfterEpoch)? false
now.compareTo(fiveSecondsAfterEpoch) > 0? true Always compare the result of compareTo() with zero: when the seconds are equal, the method returns the difference in nanoseconds, not strictly -1 or 1. Unlike LocalDate, Instant has no isEqual() method — use equals(). The same moment written in different zones, such as a ZonedDateTime of 03:30 in New York and 07:30 in UTC on March 15, 2024, gives equal Instant values after toInstant().
plus() and minus() methods
To shift an Instant, use plusSeconds(), plusMillis(), plusNanos(), the matching minus...() methods, and the generic plus(amount, ChronoUnit) / minus(...) or plus(Duration). Supported units range from NANOS to DAYS (a day counts as exactly 86,400 seconds); months, weeks and years are not supported. Every call returns a new object.
import java.time.Instant;
import java.time.temporal.ChronoUnit;
public class InstantExample4 {
public static void main(String[] args) {
Instant epoch = Instant.ofEpochSecond(0);
// Adding
Instant instant1 = epoch.plusSeconds(400);
Instant instant2 = epoch.plusMillis(98622200);
Instant instant3 = epoch.plusNanos(300013890);
Instant instant4 = epoch.plus(2, ChronoUnit.MINUTES);
// Subtracting
Instant instant5 = epoch.minusSeconds(2);
Instant instant6 = epoch.minusMillis(1);
Instant instant7 = epoch.minusNanos(1);
Instant instant8 = epoch.minus(10, ChronoUnit.SECONDS);
System.out.println(epoch);
System.out.println(instant1);
System.out.println(instant2);
System.out.println(instant3);
System.out.println(instant4);
System.out.println(instant5);
System.out.println(instant6);
System.out.println(instant7);
System.out.println(instant8);
System.out.println(epoch.plus(1, ChronoUnit.DAYS));
}
} 1970-01-01T00:00:00Z
1970-01-01T00:06:40Z
1970-01-02T03:23:42.200Z
1970-01-01T00:00:00.300013890Z
1970-01-01T00:02:00Z
1969-12-31T23:59:58Z
1969-12-31T23:59:59.999Z
1969-12-31T23:59:59.999999999Z
1969-12-31T23:59:50Z
1970-01-02T00:00:00Z What does Instant.now().plus(1, ChronoUnit.MONTHS) print? Nothing: it throws UnsupportedTemporalTypeException: Unsupported unit: Months. The length of a month depends on the calendar and the time zone, and an Instant knows neither. Add a month through a zone instead:
Instant start = Instant.parse("2024-01-31T09:00:00Z");
ZoneId zone = ZoneId.of("America/New_York");
Instant nextMonth = start.atZone(zone).plusMonths(1).toInstant();
System.out.println(nextMonth); // 2024-02-29T09:00:00Z
System.out.println(start.plusSeconds(1800)); // 2024-01-31T09:30:00Z
System.out.println(start.plusSeconds(1800).truncatedTo(ChronoUnit.HOURS)); // 2024-01-31T09:00:00Z truncatedTo() drops everything smaller than the given unit. Keep in mind that truncatedTo(ChronoUnit.DAYS) gives midnight in UTC, not local midnight — in New York in summer that is 8 PM of the previous day.
Difference between two Instants: Duration.between
The difference between two moments is returned by Duration.between(start, end) — an object with hours, minutes, seconds and nanoseconds. If you need a single number in specific units, use ChronoUnit.X.between(start, end) or start.until(end, unit): both return a long and drop incomplete units.
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
public class InstantBetweenExample {
public static void main(String[] args) {
Instant start = Instant.parse("2024-03-15T07:30:00Z");
Instant end = Instant.parse("2024-03-15T09:45:30.500Z");
Duration duration = Duration.between(start, end);
System.out.println(duration); // PT2H15M30.5S
System.out.println(duration.toMillis()); // 8130500
System.out.println(ChronoUnit.MINUTES.between(start, end)); // 135
System.out.println(start.until(end, ChronoUnit.SECONDS)); // 8130
System.out.println(Duration.between(end, start)); // PT-2H-15M-30.5S
}
} If the end is before the start, the duration is negative. Instant.now() works for measuring how long code runs only with a caveat: the system clock can be adjusted (for example, by NTP synchronisation), and the difference will be wrong, so benchmarks use System.nanoTime(). The methods of Duration itself (toMinutes(), getSeconds(), plus()) belong to the Duration lesson of this module.
How to format an Instant in Java and parse a string
instant.toString() prints the moment in ISO-8601 format in UTC (DateTimeFormatter.ISO_INSTANT), for example 2024-03-15T07:30:00.123Z. Any other format needs a DateTimeFormatter with a time zone: DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss").withZone(zone). Without withZone() the formatter cannot work out the date and the time of day.
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
public class InstantFormatExample {
public static void main(String[] args) {
Instant instant = Instant.parse("2024-03-15T07:30:00.123Z");
DateTimeFormatter newYork = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss")
.withZone(ZoneId.of("America/New_York"));
DateTimeFormatter http = DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC);
System.out.println(instant); // toString()
System.out.println(DateTimeFormatter.ISO_INSTANT.format(instant));
System.out.println(newYork.format(instant));
System.out.println(http.format(instant));
// A string in a custom format -> Instant
Instant parsed = ZonedDateTime.parse("03/15/2024 03:30:00", newYork).toInstant();
System.out.println(parsed);
}
} 2024-03-15T07:30:00.123Z
2024-03-15T07:30:00.123Z
03/15/2024 03:30:00
Fri, 15 Mar 2024 07:30:00 GMT
2024-03-15T07:30:00Z Instant.parse() accepts only the ISO format with UTC (and, in newer versions, with an offset). Strings without a zone cannot be parsed in any Java version:
Instant.parse("2024-03-15T07:30:00Z"); // 2024-03-15T07:30:00Z
Instant.parse("2024-03-15T07:30:00"); // DateTimeParseException: Text '2024-03-15T07:30:00' could not be parsed at index 19
Instant.parse("2024-03-15"); // DateTimeParseException: Text '2024-03-15' could not be parsed at index 10
Instant.parse("2024-03-15T03:30:00-04:00"); // Java 12+: 2024-03-15T07:30:00Z; Java 8-11: DateTimeParseException at index 19 An offset in the string: depends on the Java version
Up to and including Java 11, Instant.parse() understands only the Z suffix, so a string like 2024-03-15T03:30:00-04:00 from a third-party API fails with DateTimeParseException. Since Java 12, ISO_INSTANT accepts an offset too (JDK-8166138). If your code must also run on Java 8, parse such strings with OffsetDateTime.parse(text).toInstant() — this works in every version.
Pattern letters (dd, MM, HH and others) and working with Locale are covered in the DateTimeFormatter lesson of this module.
Converting Instant to ZonedDateTime, LocalDateTime and Date
To get the wall-clock date and time from an Instant, specify a time zone: instant.atZone(zone) returns a ZonedDateTime, instant.atOffset(offset) an OffsetDateTime, and LocalDateTime.ofInstant(instant, zone) a LocalDateTime. The way back to Instant is toInstant(). The bridge to the legacy API is Date.from(instant) and date.toInstant().
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Date;
public class InstantConvertExample {
public static void main(String[] args) {
Instant instant = Instant.parse("2024-03-15T07:30:00Z");
ZonedDateTime newYork = instant.atZone(ZoneId.of("America/New_York"));
OffsetDateTime minus4 = instant.atOffset(ZoneOffset.ofHours(-4));
LocalDateTime tokyo = LocalDateTime.ofInstant(instant, ZoneId.of("Asia/Tokyo"));
LocalDate date = LocalDate.ofInstant(instant, ZoneOffset.UTC); // Java 9+
Date legacy = Date.from(instant);
Instant fromLegacy = legacy.toInstant();
Instant fromMillis = Instant.ofEpochMilli(legacy.getTime());
System.out.println(newYork);
System.out.println(minus4);
System.out.println(tokyo);
System.out.println(date);
System.out.println("Hour: " + newYork.getHour() + ", " + newYork.getDayOfWeek());
System.out.println(legacy.getTime());
System.out.println(newYork.toInstant().equals(instant));
System.out.println(fromLegacy.equals(fromMillis));
}
} 2024-03-15T03:30-04:00[America/New_York]
2024-03-15T03:30-04:00
2024-03-15T16:30
2024-03-15
Hour: 3, FRIDAY
1710487800000
true
true The same Instant is 03:30 in New York and 16:30 in Tokyo: there is one moment, only the reading on the clock changes. Java 8 does not have LocalDate.ofInstant() yet; there you write instant.atZone(zone).toLocalDate(). Converting to Date reduces precision to milliseconds, so the nanoseconds are lost.
The opposite direction — from LocalDateTime to Instant — also needs a zone (ldt.atZone(zone).toInstant()); the daylight saving time details and the question of when to store LocalDateTime rather than Instant belong to the LocalDateTime lesson of this module. The full conversion table for Date, Calendar and java.sql.Date is in Date vs LocalDate: converting between the old and new API.
Java Instant methods cheat sheet
The methods of Instant fall into five groups: creating, reading epoch-based values, comparing, shifting and attaching a time zone. Every method that seems to «change» the moment returns a new object. The full list is in the official Instant Javadoc.
| Method | What it does | Edge case / error |
|---|---|---|
now(), now(Clock) | The current moment in UTC | Precision: Java 8 — milliseconds, Java 9+ — usually microseconds |
ofEpochSecond(s[, n]), ofEpochMilli(ms) | A moment relative to 1970-01-01T00:00:00Z | Negative values — before 1970; outside MIN/MAX — DateTimeException |
parse(text) | Parses an ISO string | Without Z — DateTimeParseException; an offset such as -04:00 — only since Java 12 |
getEpochSecond(), getNano(), toEpochMilli() | Seconds, nanoseconds within the second, milliseconds since the epoch | toEpochMilli() drops nanoseconds beyond the millisecond |
get(ChronoField), getLong(ChronoField) | Fields NANO/MICRO/MILLI_OF_SECOND, INSTANT_SECONDS (via getLong) | HOUR_OF_DAY and others — UnsupportedTemporalTypeException |
isAfter(), isBefore(), equals(), compareTo() | Comparing moments | Compare compareTo() with zero; there is no isEqual() |
plusSeconds(), plusMillis(), plusNanos(), minus...(), plus(n, unit) | Shift by N units | MONTHS, WEEKS, YEARS — Unsupported unit |
truncatedTo(unit) | Drops the smaller units | DAYS — midnight in UTC, not local midnight |
until(end, unit), Duration.between(a, b) | Difference between moments | If the end is before the start — a negative value |
atZone(ZoneId), atOffset(ZoneOffset) | Move to ZonedDateTime / OffsetDateTime | Without this step you cannot get the year, month or hour |
Instant gotchas and how to fix them
Almost every bug with Instant has the same root: people expect a wall-clock date and time from it, but it is only a point on the UTC time-line without a time zone. Here are the frequent cases and their fixes.
| Mistake | What happens | Correct version |
|---|---|---|
DateTimeFormatter.ofPattern("dd/MM/yyyy").format(instant) | UnsupportedTemporalTypeException: Unsupported field: DayOfMonth | ofPattern("dd/MM/yyyy").withZone(zone) |
instant.plus(1, ChronoUnit.MONTHS) | UnsupportedTemporalTypeException: Unsupported unit: Months | instant.atZone(zone).plusMonths(1).toInstant() |
| «Instant shows the wrong time, 4 hours ahead» | Not a bug: toString() always prints UTC | To show it to a user: instant.atZone(zone) or a formatter with withZone() |
Instant.parse("2024-03-15T07:30:00") | DateTimeParseException at index 19 | Add Z or use LocalDateTime.parse(s).atZone(zone).toInstant() |
instant.truncatedTo(ChronoUnit.DAYS) as «start of the day» | Midnight in UTC (8 PM of the previous day in New York in summer) | LocalDate.now(zone).atStartOfDay(zone).toInstant() |
instant.get(ChronoField.MILLI_OF_SECOND) as «milliseconds since the epoch» | Returns 0–999 within the second | instant.toEpochMilli() |
Frequently asked questions
What is the difference between Instant, ZonedDateTime and OffsetDateTime?
All three can describe the same moment, but in different ways. Instant is a bare point on the UTC time-line with no calendar fields. OffsetDateTime adds a fixed offset (-04:00), and ZonedDateTime adds a region with daylight saving rules (Europe/Berlin). Store and transfer Instant; create a ZonedDateTime when you need to show the time to a user or add calendar months.
Does Instant handle leap seconds?
No. java.time uses the Java Time-Scale: every day has exactly 86,400 seconds, and a leap second is spread over the last minutes of the day. Parsing does not reject a string with second 60 but folds it into a normal one: Instant.parse("2016-12-31T23:59:60Z") returns 2016-12-31T23:59:59Z. That keeps arithmetic with Instant predictable; exact astronomical time requires dedicated libraries.
How do I store an Instant in a database and in JSON?
JDBC 4.2 requires support for OffsetDateTime, so the most portable way is ps.setObject(1, instant.atOffset(ZoneOffset.UTC)) for a TIMESTAMP WITH TIME ZONE column; legacy code uses Timestamp.from(instant) and timestamp.toInstant(). Hibernate 5.2+ maps an Instant field on its own. In JSON, Jackson writes an Instant as a string like "2024-03-15T07:30:00Z" when JavaTimeModule is registered and WRITE_DATES_AS_TIMESTAMPS is disabled; otherwise it writes a number of seconds with a fraction.
Why use Instant if I can store a long with milliseconds?
A long does not say what it holds: milliseconds, seconds or even an ID, and mixing up the units is easy. Instant is type-safe, keeps nanoseconds, prints in readable ISO form and works directly with Duration, atZone() and formatters. Switching between the two is one line: Instant.ofEpochMilli(ms) and instant.toEpochMilli() — see Getting seconds and milliseconds.
Comments