Instant — an immutable class from the java.time package representing a point on the timeline in UTC with nanosecond precision. Unlike LocalDateTime, Instant does not contain date and time in a human-readable format — it is a machine representation of a moment. According to the Oracle Java 17 (2024) specification, Instant is designed for machine exchange of timestamps and is an analogue of System.currentTimeMillis(), but with nanosecond precision.
Key Takeaways
Instant is a class that models a single point on the timeline. Its internal representation consists of two fields: long seconds (the number of seconds from 1970-01-01T00:00:00Z) and int nanos (nanoseconds within the current second, from 0 to 999999999).
The range of Instant values is from -31557014167219200 to 31556889864403199 seconds from the epoch, covering approximately 292 million years in both directions. This is sufficient for any practical tasks, including astronomical calculations.
According to Baeldung (2024), Instant is a bridge between human-readable types (LocalDateTime, ZonedDateTime) and machine formats (timestamp in milliseconds). Instant is used for logging, caching, synchronization, and all tasks where an absolute moment in time is important.
The class implements the Comparable (for comparing moments) and Temporal (for use in the common java.time API) interfaces. Instant is immutable — all methods return a new object.
Before Java 8, java.util.Date and System.currentTimeMillis() were used to work with moments in time. Both approaches have drawbacks. Date is mutable, not thread-safe, stores time in milliseconds from the epoch, but its method names are outdated (getYear() returns 116 for 2016).
Long (a simple timestamp) is fast and compact, but has no built-in nanosecond support, is not displayed in a readable format, and requires manual parsing during debugging. The Long approach also does not distinguish data types — a developer could pass an incorrect value.
Instant solves all these problems. It is immutable, contains explicit precision information (seconds + nanoseconds), serializes to ISO-8601 format “2026-07-21T15:00:00Z”, and has a rich API for conversions. According to SonarSource (2024), Instant is the recommended replacement for Date in all new projects.
The current moment is obtained via Instant.now(). Unlike LocalDateTime.now(), Instant.now() always returns the time in UTC, ignoring the device’s time zone. This makes it ideal for server timestamps.
From existing values: Instant.ofEpochSecond(long epochSecond) — from seconds since epoch, Instant.ofEpochMilli(long epochMilli) — from milliseconds, Instant.parse(CharSequence) — from an ISO-8601 string (“2026-07-21T15:00:00Z”).
For reading: getEpochSecond() — the number of seconds since epoch, toEpochMilli() — the number of milliseconds, getNano() — nanoseconds. The toString() method returns a string in ISO-8601 format.
val now = Instant.now()
val fromSeconds = Instant.ofEpochSecond(1784700000)
val fromMillis = Instant.ofEpochMilli(1784700000000)
val parsed = Instant.parse("2026-07-21T15:00:00Z")
val epochSecond = now.getEpochSecond()
val epochMilli = now.toEpochMilli()
val nanos = now.getNano()
Instant is converted to ZonedDateTime via atZone(ZoneId). For example, Instant.now().atZone(ZoneId.of(“Europe/Moscow”)) returns a ZonedDateTime for Moscow. Without a zone, conversion is impossible — Instant does not contain calendar information.
To convert Instant to LocalDateTime: atZone(ZoneId).toLocalDateTime(). This approach is explicit and does not lose information. Reverse conversion: LocalDateTime.atZone(ZoneId).toInstant().
For compatibility with java.util.Date: Date.from(instant) and date.toInstant(). This is a bidirectional conversion that preserves precision up to milliseconds (Date does not support nanoseconds). For java.sql.Timestamp, use Timestamp.from(instant) with nanosecond support.
val instant = Instant.now()
val zoned = instant.atZone(ZoneId.of("Europe/Moscow"))
val localDateTime = instant
.atZone(ZoneId.systemDefault())
.toLocalDateTime()
val oldDate = Date.from(instant)
val backToInstant = oldDate.toInstant()
The key feature of Instant is that it is completely independent of time zones. Instant.now() returns the same result on any device anywhere in the world. This is achieved by fixing the time in UTC.
A time zone is only needed to display Instant to a human. For this, atZone(ZoneId) is used. ZoneId.systemDefault() returns the device’s time zone set in the operating system. ZoneOffset.UTC is the constant for UTC.
In distributed systems, it is recommended to store and transmit all timestamps in Instant (or OffsetDateTime with ZoneOffset.UTC). Conversion to local time is performed only on the client before displaying to the user. This prevents time zone confusion.
In distributed Android applications, time synchronization is critical for correct caching, notifications, and collaborative editing. Instant is the natural choice for this task due to its UTC anchoring.
When comparing timestamps from different devices, you need to consider that system clocks may diverge. It is recommended to use server time as a reference. The server returns Instant in UTC, and the client compares it with the local Instant only for relative calculations.
To calculate the difference between two moments, use Duration.between(Instant start, Instant end). This method returns a Duration that can be converted to hours, minutes, seconds. The isAfter() and isBefore() methods allow comparing moments.
fun isCacheExpired(
cachedAt: Instant,
ttlMinutes: Long
): Boolean {
val elapsed = Duration.between(cachedAt, Instant.now())
return elapsed.toMinutes() >= ttlMinutes
}
The first example is logging events with a timestamp. Instant is saved to the Room database and sent to the server. The timestamp is logged in UTC for unambiguous interpretation.
data class EventLog(
val id: Long = 0,
val eventName: String,
val timestamp: Instant
)
class Converters {
@TypeConverter
fun fromInstant(value: Instant?): Long? {
return value?.toEpochMilli()
}
@TypeConverter
fun toInstant(value: Long?): Instant? {
return value?.let { Instant.ofEpochMilli(it) }
}
}
The second example is determining the time elapsed since an event. We use Duration.between to display “5 minutes ago”, “2 hours ago” — a format common in messengers and social networks.
fun timeAgo(instant: Instant): String {
val duration = Duration.between(instant, Instant.now())
return when {
duration.toMinutes() < 1 -> "just now"
duration.toHours() < 1 -> "${duration.toMinutes()} min ago"
duration.toDays() < 1 -> "${duration.toHours()} h ago"
else -> "${duration.toDays()} d ago"
}
}
The third example is data synchronization between server and client. We use Instant to track the last update time.
class SyncManager {
private var lastSyncAt: Instant? = null
fun sync() {
val syncStart = Instant.now()
// server request with lastSyncAt
lastSyncAt = syncStart
}
fun shouldSync(intervalMinutes: Long): Boolean {
val last = lastSyncAt ?: return true
return Duration.between(last, Instant.now())
.toMinutes() >= intervalMinutes
}
}
The first mistake is using Instant.now().toString() for display to the user. Instant outputs in UTC format “2026-07-21T15:00:00Z”, which is unreadable for humans. Always convert Instant via atZone() to the local time zone before displaying.
The second mistake is losing nanoseconds when converting to java.util.Date. Date only supports milliseconds. If Instant has nanoseconds, they will be discarded in Date.from(instant). Use Instant.truncatedTo(ChronoUnit.MILLIS) to explicitly specify precision.
The third mistake is confusion between toEpochMilli() and getEpochSecond(). toEpochMilli() returns the number of milliseconds since epoch (long), while getEpochSecond() returns the number of seconds (long). Mixing these methods can result in a 1000x error.
The fourth mistake is assuming that Instant.now() is synchronized across all devices. System clocks can differ by minutes or even hours. For time-critical operations (authentication, payments), use server Instant as the source of truth.
Frequently Asked Questions
System.currentTimeMillis() returns a long — the number of milliseconds since epoch without time zone binding. Instant provides the same functionality but with nanosecond precision and a rich API for conversions, comparisons, and compatibility with java.time.
Room does not support Instant directly. Use TypeConverter that converts Instant to Long (toEpochMilli) and back (Instant.ofEpochMilli). For nanosecond precision, save two fields: epoch-seconds and nanoseconds.
Yes, Instant is immutable and correctly implements equals() and hashCode(). Two Instants with the same value will be equal. This makes it a reliable key for HashMap and other collections, unlike mutable java.util.Date.
Use Duration.between(start, end) to get a Duration or ChronoUnit.SECONDS.between(start, end) for the difference in seconds (long). Duration provides toMinutes(), toHours(), toDays(), and toNanos() methods.
Instant is designed as an absolute point on the timeline. Without specifying a time zone or UTC, parsing is impossible because Instant does not contain calendar information. The “Z” suffix denotes zero offset (UTC) and is mandatory for the ISO-8601 format.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also