Java Instant Class

Su Suriya Ravichandran Updated 14 Nov 2026
9 min read ·Lesson 122 of 133

What an Instant Represents

java.time.Instant represents a single point on the global timeline. It doesn't store a time zone, a calendar date or a local clock reading. Internally it holds only two numbers:

FieldTypeMeaning
secondslongSeconds since the epoch, 1970-01-01T00:00:00Z
nanosintExtra nanoseconds within that second (0 – 999,999,999)

The epoch is a fixed reference moment that computers agreed on. Because an Instant is counted from that one moment, the same Instant means exactly the same moment for a server in Mumbai, London or Virginia. That is the reason to use it: it is the "machine time" type of java.time.

Compare it with the other date-time types:

TypeStoresAnswers the question
LocalDateTimedate + wall-clock time, no zone"What does the calendar/clock say?"
ZonedDateTimedate + time + zone rules"What time is it in Chennai at this moment?"
OffsetDateTimedate + time + fixed offset"What time is it at +05:30?"
Instantseconds + nanos from epoch, always UTC"When exactly did this happen?"

Use Instant for timestamps: log entries, database audit columns (created_at), event ordering, token expiry, cache expiry and measuring elapsed time between two events. Don't use it when a human calendar concept matters — "every month on the 5th", "9 AM local time", birthdays — because an Instant has no idea what month or local hour it is.

Creating an Instant

import java.time.Instant;

public class CreateInstant {
    public static void main(String[] args) {
        Instant now = Instant.now();                                  // current moment
        Instant parsed = Instant.parse("2026-11-14T05:54:30.123Z");   // ISO-8601 text
        Instant fromSeconds = Instant.ofEpochSecond(0);               // the epoch
        Instant fromMillis = Instant.ofEpochMilli(1_794_657_270_123L);

        System.out.println(now);
        System.out.println(parsed);
        System.out.println(fromSeconds);
        System.out.println(fromMillis);
        System.out.println(Instant.EPOCH);
    }
}

Output (the first line depends on when you run it):

2026-11-14T05:54:31.482917Z
2026-11-14T05:54:30.123Z
1970-01-01T00:00:00Z
2026-11-14T11:54:30.123Z
1970-01-01T00:00:00Z

How each factory works:

  • Instant.now() reads the system UTC clock. Its precision depends on the operating system — often microseconds on Linux, sometimes milliseconds on older systems. For testable code, prefer Instant.now(clock) with an injected java.time.Clock, so a test can supply a fixed time.
  • Instant.parse(text) accepts ISO-8601 text. Since Java 12 it also accepts an offset such as 2026-11-14T11:24:30+05:30, converting it to UTC. A string with no zone or offset (for example 2026-11-14T05:54:30) throws DateTimeParseException, because without a zone the text does not identify one moment.
  • Instant.ofEpochSecond(long) and Instant.ofEpochMilli(long) build an instant from numeric timestamps, the format used by Unix systems, JWT tokens (iat, exp in seconds) and System.currentTimeMillis() (milliseconds). Mixing the two up is a very common error: passing milliseconds to ofEpochSecond produces a date tens of thousands of years in the future.

The toString() output always ends in Z ("Zulu", meaning UTC). Notice the fourth line: the number 1_794_657_270_123 milliseconds is not the same moment as the parsed instant — it is exactly six hours later. A timestamp is just a number; nothing protects you from using the wrong one, so name variables clearly (expiresAtEpochSeconds, not exp).

Reading the Parts of an Instant

Instant i = Instant.parse("2026-11-14T05:54:30.123Z");

System.out.println(i.getEpochSecond()); // 1794635670
System.out.println(i.getNano());        // 123000000
System.out.println(i.toEpochMilli());   // 1794635670123
  • getEpochSecond() returns the whole seconds since the epoch.
  • getNano() returns only the fraction of the current second, in nanoseconds — not the total nanoseconds since the epoch. .123 seconds is 123,000,000 nanoseconds.
  • toEpochMilli() combines both into milliseconds, which is what most databases, JavaScript (Date.getTime()) and many APIs expect. It truncates any precision finer than a millisecond and throws ArithmeticException if the value doesn't fit in a long.

An Instant has no getYear(), getMonth() or getHour(). Those values depend on a time zone — 05:54 UTC is 11:24 in India — so to read them you must first convert the instant to a zoned type (covered below).

Instant Arithmetic

Instant is immutable. Every arithmetic method returns a new Instant; the original never changes. If you write i.plusSeconds(60); without assigning the result, nothing happens.

import java.time.*;
import java.time.temporal.ChronoUnit;

public class InstantMath {
    public static void main(String[] args) {
        Instant i = Instant.parse("2026-11-14T05:54:30.123Z");

        System.out.println(i.plusSeconds(3600));
        System.out.println(i.minus(Duration.ofDays(1)));
        System.out.println(i.truncatedTo(ChronoUnit.SECONDS));
        System.out.println(i.plus(1, ChronoUnit.DAYS));

        try {
            i.plus(1, ChronoUnit.MONTHS);
        } catch (Exception e) {
            System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage());
        }
    }
}

Output:

2026-11-14T06:54:30.123Z
2026-11-13T05:54:30.123Z
2026-11-14T05:54:30Z
2026-11-15T05:54:30.123Z
UnsupportedTemporalTypeException: Unsupported unit: Months

What happens here:

  1. plusSeconds, plusMillis, plusNanos (and the minus versions) shift the moment by an exact amount.
  2. minus(Duration) works with any Duration, which is the natural partner of Instant — both measure exact time.
  3. truncatedTo(ChronoUnit.SECONDS) drops the fraction. This is useful when a database column stores only seconds or milliseconds and you want values to compare equal after a round trip.
  4. ChronoUnit.DAYS is allowed, but for an Instant a "day" always means exactly 86,400 seconds. It is not a calendar day.
  5. MONTHS and YEARS throw UnsupportedTemporalTypeException, because their length depends on the calendar and the time zone (28–31 days, 365–366 days). An Instant has neither, so Java refuses rather than guessing.

To add one calendar month, convert to a zoned value, do the calendar arithmetic, then convert back:

Instant nextMonth = i.atZone(ZoneId.of("Asia/Kolkata"))
                     .plusMonths(1)
                     .toInstant();

The zone matters: near midnight, "one month later in Kolkata" and "one month later in New York" can fall on different moments.

Comparing Instants

Instant i = Instant.parse("2026-11-14T05:54:30.123Z");

System.out.println(i.isAfter(Instant.EPOCH));      // true
System.out.println(i.compareTo(i.plusMillis(1)));  // -1000000
System.out.println(Instant.parse("2026-11-14T05:54:30Z")
        .equals(Instant.parse("2026-11-14T11:24:30+05:30"))); // true
  • isBefore, isAfter and equals are the readable way to compare moments.
  • compareTo returns a negative, zero or positive number. Only the sign is meaningful — here it happens to be the nanosecond difference (-1000000), so never test compareTo(...) == -1.
  • The last line prints true: 05:54:30 UTC and 11:24:30 at +05:30 are the same moment. Once parsed into an Instant, the original offset is gone; only the moment remains. This is exactly what you want for comparing events from different regions.

Because Instant implements Comparable<Instant>, lists of instants sort chronologically with Collections.sort or Comparator.naturalOrder().

Converting Instant to Zoned Types

To show a timestamp to a person, attach a zone or an offset:

Instant i = Instant.parse("2026-11-14T05:54:30.123Z");

ZonedDateTime india = i.atZone(ZoneId.of("Asia/Kolkata"));
OffsetDateTime utc  = i.atOffset(ZoneOffset.UTC);

System.out.println(india); // 2026-11-14T11:24:30.123+05:30[Asia/Kolkata]
System.out.println(utc);   // 2026-11-14T05:54:30.123Z
System.out.println(india.getHour());       // 11
System.out.println(india.toInstant().equals(i)); // true
  • atZone(ZoneId) returns a ZonedDateTime, applying the zone's rules (including daylight saving time where it exists).
  • atOffset(ZoneOffset) returns an OffsetDateTime with a fixed offset.
  • toInstant() goes back the other way. The round trip loses nothing: it is the same moment, only viewed through a different clock.

The usual architecture in real applications is: store and transmit Instant (UTC); convert to the user's zone only at the edge, when rendering a page, email or report. This avoids the classic bug where two servers in different zones write LocalDateTime values that look comparable but aren't.

Converting to LocalDateTime requires a zone too: LocalDateTime.ofInstant(i, ZoneId.of("Europe/London")).

Range of Instant

System.out.println(Instant.MIN); // -1000000000-01-01T00:00:00Z
System.out.println(Instant.MAX); // +1000000000-12-31T23:59:59.999999999Z

The range is about one billion years on each side of the epoch, far beyond any real application. Some systems use Instant.MAX as a sentinel meaning "never expires", but be careful: Instant.MAX.plusSeconds(1) throws DateTimeException, and toEpochMilli() on it throws ArithmeticException. A nullable or Optional<Instant> field is usually a clearer way to say "no expiry".

Practical Example: Ordering Logs from Several Regions

A payment flow runs on servers in three regions. Each server writes its own local time with an offset. To understand what happened, you need the events in true order, with the gap from the first event.

import java.time.*;
import java.util.*;

public class MultiRegionLog {
    record Event(String source, Instant at, String message) { }

    public static void main(String[] args) {
        List<Event> events = new ArrayList<>(List.of(
            new Event("mumbai-api",
                OffsetDateTime.parse("2026-11-14T11:24:05.300+05:30").toInstant(), "payment received"),
            new Event("london-worker",
                OffsetDateTime.parse("2026-11-14T05:54:05.100Z").toInstant(), "invoice generated"),
            new Event("virginia-mailer",
                OffsetDateTime.parse("2026-11-14T00:54:06.000-05:00").toInstant(), "receipt emailed"),
            new Event("mumbai-api",
                OffsetDateTime.parse("2026-11-14T11:24:04.900+05:30").toInstant(), "order placed")));

        events.sort(Comparator.comparing(Event::at));
        Instant first = events.get(0).at();

        for (Event e : events) {
            System.out.printf("%s  +%4d ms  %-15s %s%n",
                e.at(), Duration.between(first, e.at()).toMillis(), e.source(), e.message());
        }
    }
}

Output:

2026-11-14T05:54:04.900Z  +   0 ms  mumbai-api      order placed
2026-11-14T05:54:05.100Z  + 200 ms  london-worker   invoice generated
2026-11-14T05:54:05.300Z  + 400 ms  mumbai-api      payment received
2026-11-14T05:54:06Z  +1100 ms  virginia-mailer receipt emailed

Step by step:

  1. Parsing with offsets. Each log line carries its own offset (+05:30, Z, -05:00). OffsetDateTime.parse(...).toInstant() converts each to the same UTC timeline. If these strings were parsed as LocalDateTime, Virginia's 00:54 would sort first and Mumbai's 11:24 last — completely wrong.
  2. Sorting. Comparator.comparing(Event::at) uses the natural order of Instant. The true order is order → invoice → payment → receipt.
  3. Elapsed time. Duration.between(first, e.at()) gives the exact gap, converted to milliseconds for display.
  4. Formatting detail. The last line prints 05:54:06Z, not 05:54:06.000Z. Instant.toString() uses the shortest ISO form and omits a zero fraction, which is why that row is narrower. For fixed-width logs, format through a DateTimeFormatter with an explicit pattern, for example DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX").withZone(ZoneOffset.UTC).

Note that this only orders events correctly if the servers' clocks are synchronised (normally via NTP). An Instant is only as accurate as the clock that produced it.

Practical Example: Checking Token Expiry

Authentication tokens such as JWTs store times as epoch seconds. A server checks whether a token is still valid by comparing the expiry with the current instant.

import java.time.*;

public class TokenExpiry {
    public static void main(String[] args) {
        long issuedAt = 1_794_635_070L;                     // "iat" claim, epoch seconds
        Instant expires = Instant.ofEpochSecond(issuedAt)
                                 .plus(Duration.ofMinutes(15));
        Instant now = Instant.parse("2026-11-14T05:54:30Z"); // in production: Instant.now(clock)

        System.out.println("Token issued " + Instant.ofEpochSecond(issuedAt)
            + ", expires " + expires
            + ", valid now: " + now.isBefore(expires));
    }
}

Output:

Token issued 2026-11-14T05:44:30Z, expires 2026-11-14T05:59:30Z, valid now: true

How it works:

  • Instant.ofEpochSecond(issuedAt) turns the numeric claim into a moment. Using ofEpochMilli here would place the token in January 1970 and make it look expired.
  • plus(Duration.ofMinutes(15)) computes the expiry exactly, independent of time zones or daylight saving.
  • now.isBefore(expires) decides validity. Real servers often allow a small clock skew tolerance between machines, for example now.isBefore(expires.plusSeconds(30)).
  • The current time is passed in as a fixed value. In production code, inject a Clock and call Instant.now(clock); a unit test can then use Clock.fixed(...) to test "just before expiry" and "just after expiry" without waiting.

Measuring Elapsed Time: Instant vs System.nanoTime()

Instant is the right choice for recording when something happened. For measuring how long code takes, System.nanoTime() is more reliable, because the wall clock behind Instant.now() can jump when NTP corrects it or an administrator changes the system time, while nanoTime() is monotonic.

long start = System.nanoTime();
// ... work ...
Duration took = Duration.ofNanos(System.nanoTime() - start);
NeedUse
Timestamp stored in a database or logInstant
Compare events from different machinesInstant (with synchronised clocks)
Benchmark or time a method in one JVMSystem.nanoTime()
Show a time to a userInstant → ZonedDateTime in the user's zone

Instant with Legacy APIs and Databases

Older APIs still appear in many projects, and Instant is the bridge to them:

java.util.Date legacy = java.util.Date.from(instant);
Instant back = legacy.toInstant();

java.sql.Timestamp ts = java.sql.Timestamp.from(instant);
Instant fromDb = ts.toInstant();

With JDBC 4.2 drivers you can usually avoid Timestamp entirely. For a TIMESTAMP WITH TIME ZONE column, read and write OffsetDateTime (rs.getObject("created_at", OffsetDateTime.class)) and call toInstant(). Driver support for reading an Instant directly varies, so check your driver's documentation. In JPA/Hibernate 6, entity fields of type Instant map to timestamp columns out of the box, which is why many teams use Instant createdAt as the standard audit field.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Java Instant Class and get answers drawn from it.

Signed-in readers only.