Duration Class
java.time.Duration represents an exact amount of time, measured in seconds and nanoseconds, such as "2 hours 15 minutes" or "1.5 seconds." It is the right type for elapsed time and machine-scale intervals: request latency, timeouts, session lengths, cache expiry, retry delays, video lengths, and shift hours. Unlike Period, a Duration is not tied to the calendar: one day in a Duration is always exactly 24 hours.
Creating a Duration
import java.time.Duration;
import java.time.temporal.ChronoUnit;
Duration.ofMinutes(135); // PT2H15M
Duration.ofSeconds(90); // PT1M30S
Duration.ofMillis(1500); // PT1.5S
Duration.ofHours(8); // PT8H
Duration.ofDays(2); // PT48H: always 48 hours
Duration.of(3, ChronoUnit.HALF_DAYS); // PT36H
Duration.parse("PT1H30M"); // from ISO-8601 text
Duration.ZERO; // PT0S
The ISO-8601 form starts with PT (period of time) followed by hours (H), minutes (M), and seconds (S, with an optional fraction). toString() produces it, and parse reads it, which makes durations easy to store in configuration files: session.timeout=PT30M.
Measuring Between Two Values
Duration.between(LocalTime.of(9, 15), LocalTime.of(17, 45)); // PT8H30M
Instant t1 = Instant.parse("2026-11-13T10:00:00Z");
Instant t2 = Instant.parse("2026-11-13T10:00:01.250Z");
Duration.between(t1, t2); // PT1.25S
Duration.between works with time-based types: Instant, LocalTime, LocalDateTime, ZonedDateTime, and OffsetDateTime. It does not work with LocalDate, which has no time of day:
Duration.between(LocalDate.of(2026, 1, 1), LocalDate.of(2026, 1, 2));
// UnsupportedTemporalTypeException
For dates, use Period.between or ChronoUnit.DAYS.between.
The result is negative if the second argument is earlier than the first: Duration.ofSeconds(-90) prints as PT-1M-30S.
Measuring elapsed time in code
long start = System.nanoTime();
runReport();
Duration elapsed = Duration.ofNanos(System.nanoTime() - start);
For measuring how long code takes, use System.nanoTime(), which is monotonic: it cannot jump if the system clock is adjusted. Instant.now() is for knowing what time it is, not for timing operations.
Converting and Reading Parts
Two families of methods extract values, and confusing them is a common error:
| Method family | Returns | Duration.ofMinutes(135) |
|---|---|---|
toDays(), toHours(), toMinutes(), toSeconds(), toMillis(), toNanos() | The total amount in that unit | toHours() → 2, toMinutes() → 135 |
toDaysPart(), toHoursPart(), toMinutesPart(), toSecondsPart(), toMillisPart() | The component of that unit (Java 9+) | toHoursPart() → 2, toMinutesPart() → 15 |
To display "2h 15m," use toHours() and toMinutesPart(). Using toMinutes() would print "2h 135m." The to*Part methods are designed exactly for building such displays:
Duration.ofHours(25).toDaysPart(); // 1
Duration.ofHours(25).toHoursPart(); // 1
getSeconds() returns the total seconds and getNano() the nanosecond remainder, matching how a Duration is stored internally.
Arithmetic
Durations are immutable; every operation returns a new one:
Duration.ofHours(1).plusMinutes(15).multipliedBy(3); // PT3H45M
Duration.ofMinutes(100).dividedBy(4); // PT25M
Duration.ofMinutes(100).dividedBy(Duration.ofMinutes(30)); // 3: how many whole 30-minute blocks
Duration.ofMinutes(10).minus(Duration.ofSeconds(30)); // PT9M30S
Duration.ofSeconds(3725).truncatedTo(ChronoUnit.MINUTES); // PT1H2M
Duration.ofSeconds(-90).abs(); // PT1M30S
dividedBy(long) truncates any fraction of a nanosecond; dividedBy(Duration) (Java 9+) returns a whole count.
Comparing
Duration slo = Duration.ofSeconds(2);
Duration latency = Duration.ofMillis(2300);
latency.compareTo(slo) > 0; // true: slower than the target
latency.isNegative(); // false
Duration.ZERO.isZero(); // true
Duration implements Comparable, so latencies can be sorted and the slowest found with Collections.max.
Applying a Duration
Instant now = Instant.parse("2026-11-13T10:00:00Z");
Instant expires = now.plus(Duration.ofMinutes(30)); // 2026-11-13T10:30:00Z
LocalTime.of(23, 30).plus(Duration.ofHours(2)); // 01:30 (wraps around midnight)
ZonedDateTime.now().plus(Duration.ofDays(1)); // exactly 24 hours later, even across DST
On a ZonedDateTime, plus(Duration.ofDays(1)) adds 24 real hours, while plus(Period.ofDays(1)) moves to the same local time on the next calendar day. On the night clocks change for daylight-saving time, those two results differ by an hour.
Duration in APIs
Many Java APIs accept a Duration directly, which avoids ambiguous plain numbers ("is this 5000 milliseconds or seconds?"):
HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5));
HttpRequest.newBuilder(uri).timeout(Duration.ofSeconds(10));
Thread.sleep(Duration.ofMillis(500)); // Java 19+
Accepting Duration parameters in your own methods makes call sites self-explanatory.
Practical Example: Latency Reports and Session Expiry
A backend service tracks request latencies against a two-second service-level objective (SLO), and expires user sessions after 30 minutes of inactivity or 8 hours in total, whichever comes first.
import java.time.*;
import java.util.*;
public class Dur {
static String human(Duration d) {
if (d.isNegative()) return "-" + human(d.negated());
if (d.toDays() > 0) return d.toDays() + "d " + d.toHoursPart() + "h";
if (d.toHours() > 0) return d.toHours() + "h " + d.toMinutesPart() + "m";
if (d.toMinutes() > 0) return d.toMinutes() + "m " + d.toSecondsPart() + "s";
return d.toMillis() + " ms";
}
public static void main(String[] args) {
long[] latenciesMs = {120, 85, 2300, 450, 95, 6100, 130};
Duration slo = Duration.ofSeconds(2);
Duration total = Duration.ZERO;
int breaches = 0;
for (long ms : latenciesMs) {
Duration lat = Duration.ofMillis(ms);
total = total.plus(lat);
if (lat.compareTo(slo) > 0) breaches++;
}
System.out.println("Average latency: " + human(total.dividedBy(latenciesMs.length)) + ", SLO breaches: " + breaches);
Instant login = Instant.parse("2026-11-13T09:15:00Z");
Duration idleTimeout = Duration.ofMinutes(30), absolute = Duration.ofHours(8);
Instant lastActivity = Instant.parse("2026-11-13T16:50:00Z");
Instant now = Instant.parse("2026-11-13T17:10:00Z");
Instant expires = Collections.min(List.of(lastActivity.plus(idleTimeout), login.plus(absolute)));
System.out.println("Session expires at " + expires + ", time left " + human(Duration.between(now, expires)));
System.out.println(human(Duration.ofHours(53)) + " | " + human(Duration.ofSeconds(-125)));
}
}
Output:
Average latency: 1325 ms, SLO breaches: 2
Session expires at 2026-11-13T17:15:00Z, time left 5m 0s
2d 5h | -2m 5s
The latency report accumulates Durations with plus, averages them with dividedBy, and counts requests slower than the SLO with compareTo: two requests (2.3 s and 6.1 s) breach the 2-second target. The session check computes two candidate expiry instants, 30 minutes after the last activity (17:20) and 8 hours after login (17:15), and takes the earlier one with Collections.min, since Instant is comparable. human turns any duration into a short readable string by picking the largest relevant unit and pairing a to* total with the matching to*Part component; it handles negative durations by formatting the absolute value with a minus sign.