How to convert currentTimeMillis to a date in Java?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
The modern way to convert System.currentTimeMillis() to a date in Java is through the java.time API introduced in Java 8. Use Instant.ofEpochMilli() to create an instant from the millisecond value, then convert to a ZonedDateTime or LocalDateTime for display and formatting. The legacy Date class still works but should be avoided in new code.
What currentTimeMillis Returns
System.currentTimeMillis() returns a long representing the number of milliseconds elapsed since the Unix epoch (1970-01-01T00:00:00Z). This value is always UTC-based and timezone-independent. It is commonly used for timestamps, performance measurement, and cache expiration.
The value itself has no timezone information. Converting it to a human-readable date requires choosing a timezone explicitly or using the system default.
Modern Approach: java.time (Java 8+)
Instant
Instant represents a point on the UTC timeline. It is the natural first step when converting from epoch milliseconds.
Instant always prints in UTC (the trailing Z stands for Zulu/UTC). This is useful for logging and storage because it removes timezone ambiguity.
ZonedDateTime
To display the timestamp in a specific timezone:
LocalDateTime
LocalDateTime drops the timezone information entirely. Use it when you need a date and time without timezone context (for example, a user-facing display where the timezone is handled elsewhere).
DateTimeFormatter
For custom output formats:
DateTimeFormatter is thread-safe and immutable. You can declare it as a static final field and reuse it safely across threads.
Legacy Approach: java.util.Date and SimpleDateFormat
These classes still work and appear frequently in older codebases.
Date
SimpleDateFormat
API Comparison
| Class | Package | Thread-Safe | Timezone Handling | Java Version |
Instant | java.time | Yes | UTC only | 8+ |
ZonedDateTime | java.time | Yes | Explicit timezone | 8+ |
LocalDateTime | java.time | Yes | No timezone | 8+ |
DateTimeFormatter | java.time | Yes | Configurable | 8+ |
Date | java.util | No | System default | 1.0+ |
SimpleDateFormat | java.text | No | Configurable | 1.1+ |
The critical difference is thread safety. SimpleDateFormat is not thread-safe, meaning sharing a single instance across threads produces corrupt output or exceptions. DateTimeFormatter has no such issue.
Converting Between Legacy and Modern APIs
When working with libraries that still use java.util.Date, convert at the boundary:
This pattern keeps your internal code on java.time while maintaining compatibility with older APIs.
Handling Timestamps from External Systems
When receiving epoch milliseconds from APIs, databases, or message queues, always clarify whether the value is in milliseconds or seconds. A common mistake is passing seconds to Instant.ofEpochMilli(), which produces a date in January 1970.
A quick check: if the value has 13 digits, it is likely milliseconds. If it has 10 digits, it is likely seconds.
Formatting Common Date Patterns
Here are the most frequently needed date format patterns with DateTimeFormatter:
For ISO 8601 output, prefer the built-in DateTimeFormatter.ISO_INSTANT or ISO_OFFSET_DATE_TIME constants over custom patterns. They handle edge cases like leap seconds and timezone offset formatting correctly.
Common Pitfalls
Sharing a SimpleDateFormat instance across threads is the most common production bug related to date formatting. The formatter maintains internal mutable state, and concurrent access corrupts it silently. Use DateTimeFormatter or create a new SimpleDateFormat per thread.
Using LocalDateTime to store timestamps loses timezone information. If two servers in different timezones both store LocalDateTime, you cannot compare their timestamps correctly. Use Instant or ZonedDateTime for storage and interchange.
Confusing milliseconds with seconds when calling Instant.ofEpochMilli() versus Instant.ofEpochSecond() produces dates 50 years in the past.
Calling new Date() instead of new Date(millis) captures the current time, not the time represented by a specific millisecond value. This is obvious in isolation but causes bugs in refactored code where the millis variable is no longer passed through.
Ignoring timezone when formatting dates for users in different regions produces incorrect local times. Always use an explicit ZoneId rather than relying on the server's system default.
Mixing java.util.Date and java.time types without clear boundary conversion leads to code that is difficult to reason about. Establish a project convention: use java.time internally and convert to Date only at the edges where legacy APIs require it.
Summary
- Use
Instant.ofEpochMilli(millis)to convertcurrentTimeMillisto a point in time. - Use
ZonedDateTimewhen you need timezone-aware display; useInstantfor UTC storage. - Format output with
DateTimeFormatter, which is thread-safe and reusable. - Avoid
SimpleDateFormatin new code due to thread-safety issues. - Convert between
DateandInstantat API boundaries usingtoInstant()andDate.from(). - Verify whether external timestamps are in milliseconds or seconds before converting.

