Java Tutorial
🔍

Java Duration Class

Java Duration Class

Duration represents a precise, time-based span — so many seconds and nanoseconds — the exact counterpart to Period's calendar-based amounts. It is the class for anything measured in fixed units that never change meaning depending on context: an API's response time, a video's playback length, how long a batch job took to run. Introduced in Java 8 alongside Period, Duration is immutable, and unlike Period, it implements Comparable<Duration> directly — two durations can always be meaningfully compared, because a second is a second no matter where it falls on the calendar.

What Is Duration?

java.time.Duration stores a span of time internally as a whole number of seconds plus a nanosecond adjustment, giving it precision down to a single nanosecond. Unlike Period, it has no concept of years, months, or calendar days at all — Duration.ofDays(1) is defined as exactly 24 hours, which is a genuinely different thing from a calendar day, since a calendar day can be 23 or 25 hours long across a daylight saving transition.

Duration.between() computes the gap between two Temporal values that support time-based measurement — Instant, LocalTime, LocalDateTime, ZonedDateTime. Duration.of(), ofSeconds(), ofMinutes(), ofHours(), and ofDays() construct one directly, with no dates or times involved at all.

One sentence before the diagram: Period and Duration model the same general idea — an amount of time — from two genuinely different angles.

Period                              Duration
-------                             --------
years, months, days                 seconds, nanoseconds
"1 month" varies in real length     "24 hours" is always 24 real hours
applies to LocalDate/LocalDateTime  applies to Instant/LocalTime/
                                     LocalDateTime/ZonedDateTime
not Comparable                      implements Comparable<Duration>

Period.ofDays(1)  != always 24 hours (calendar day can be 23-25h across DST)
Duration.ofDays(1) == always exactly 24 hours, no exceptions

Duration always normalizes into the same internal seconds-plus-nanoseconds representation regardless of which factory method built it, which is exactly why equals() correctly reports two differently-constructed but equal-length durations as equal — unlike Period, whose equals() compares raw stored fields.

Why Duration Was Introduced

Before Java 8, measuring or representing an elapsed span of time usually meant a raw long holding a millisecond count, with nothing in the type system saying what unit that number was actually in.

1// File: BeforeDuration.java 2 3public class BeforeDuration { 4 public static void main(String[] args) { 5 long requestStartMillis = 1000L; 6 long requestEndMillis = 1450L; 7 8 // Just a raw number - nothing here says "this is milliseconds" 9 // to anyone reading the variable at a random point in the codebase 10 long elapsedMillis = requestEndMillis - requestStartMillis; 11 12 System.out.println("Elapsed: " + elapsedMillis + " ms"); 13 } 14}
Output:
Elapsed: 450 ms

Duration gives that same 450 milliseconds an actual type, one that carries its own unit and prints in a form that is impossible to misread as the wrong scale.

1// File: AfterDuration.java 2import java.time.*; 3 4public class AfterDuration { 5 public static void main(String[] args) { 6 Duration elapsed = Duration.ofMillis(450); 7 8 System.out.println("Elapsed: " + elapsed.toMillis() + " ms"); 9 System.out.println("As a Duration: " + elapsed); 10 } 11}
Output:
Elapsed: 450 ms
As a Duration: PT0.45S

Both versions represent the same 450-millisecond span. Only the second one can never be silently added to a value measured in a different unit without a compile error somewhere along the way.

Syntax

Duration can be built from an explicit ChronoUnit, or from one of several convenience factory methods, and it breaks back down into hours, minutes, and seconds cleanly.

1// File: DurationSyntaxForms.java 2import java.time.*; 3import java.time.temporal.*; 4 5public class DurationSyntaxForms { 6 public static void main(String[] args) { 7 Duration fromUnit = Duration.of(90, ChronoUnit.MINUTES); 8 Duration fromSeconds = Duration.ofSeconds(5400); 9 Duration fromHours = Duration.ofHours(2); 10 11 System.out.println("of(90, MINUTES): " + fromUnit); 12 System.out.println("ofSeconds(5400): " + fromSeconds); 13 System.out.println("ofHours(2): " + fromHours); 14 15 Duration combined = fromUnit.plus(fromHours); 16 System.out.println("fromUnit.plus(fromHours): " + combined); 17 18 System.out.println("Hours part: " + combined.toHoursPart()); 19 System.out.println("Minutes part: " + combined.toMinutesPart()); 20 System.out.println("Total minutes: " + combined.toMinutes()); 21 22 Duration doubled = fromHours.multipliedBy(2); 23 System.out.println("fromHours.multipliedBy(2): " + doubled); 24 25 // Duration implements Comparable, unlike Period 26 System.out.println("fromUnit.compareTo(fromHours): " + fromUnit.compareTo(fromHours)); 27 } 28}
Output:
of(90, MINUTES): PT1H30M
ofSeconds(5400): PT1H30M
ofHours(2): PT2H
fromUnit.plus(fromHours): PT3H30M
Hours part: 3
Minutes part: 30
Total minutes: 210
fromHours.multipliedBy(2): PT4H
fromUnit.compareTo(fromHours): -1

fromUnit and fromSeconds were constructed two completely different ways, yet both print identically — Duration always normalizes into the same internal seconds-and-nanoseconds representation, regardless of how it was built.

Common Use Cases

Comparing a Measured Duration Against a Threshold

compareTo() lets a Duration be checked directly against a configured limit, with no manual unit conversion needed on either side.

1// File: DurationComparisonExample.java 2import java.time.*; 3 4public class DurationComparisonExample { 5 public static void main(String[] args) { 6 Duration sla = Duration.ofMillis(500); 7 Duration actualResponseTime = Duration.ofMillis(320); 8 9 boolean withinSla = actualResponseTime.compareTo(sla) <= 0; 10 11 System.out.println("Within SLA: " + withinSla); 12 } 13}
Output:
Within SLA: true

Formatting a Duration for Display

toHours(), toMinutesPart(), and toSecondsPart() combine into a readable "Xh Ym Zs" style breakdown that a stopwatch or video player would actually show.

1// File: DurationFormattingExample.java 2import java.time.*; 3 4public class DurationFormattingExample { 5 public static void main(String[] args) { 6 Duration videoLength = Duration.ofSeconds(5025); 7 8 String formatted = videoLength.toHours() + "h " 9 + videoLength.toMinutesPart() + "m " 10 + videoLength.toSecondsPart() + "s"; 11 12 System.out.println(formatted); 13 } 14}
Output:
1h 23m 45s

Computing a Deadline From a Timeout

Instant.plus(Duration) is the standard way to compute a precise deadline moment from a fixed timeout span, distinct from applying a Period to a LocalDate.

1// File: TimeoutDeadlineExample.java 2import java.time.*; 3 4public class TimeoutDeadlineExample { 5 public static void main(String[] args) { 6 Instant requestReceivedAt = Instant.parse("2026-08-25T09:00:00Z"); 7 Duration timeout = Duration.ofSeconds(30); 8 9 Instant deadline = requestReceivedAt.plus(timeout); 10 11 System.out.println("Deadline: " + deadline); 12 } 13}
Output:
Deadline: 2026-08-25T09:00:30Z

Validating a Measured Duration Before Trusting It

isNegative() and isZero() catch an invalid measurement immediately, before a negative or empty span causes confusing downstream behavior.

1// File: DurationZeroValidationExample.java 2import java.time.*; 3 4public class DurationZeroValidationExample { 5 public static void main(String[] args) { 6 Instant start = Instant.parse("2026-08-25T09:00:10Z"); 7 Instant end = Instant.parse("2026-08-25T09:00:05Z"); 8 9 Duration measured = Duration.between(start, end); 10 11 if (measured.isNegative()) { 12 System.out.println("Invalid measurement: end occurred before start"); 13 } else if (measured.isZero()) { 14 System.out.println("No time elapsed"); 15 } else { 16 System.out.println("Elapsed: " + measured); 17 } 18 } 19}
Output:
Invalid measurement: end occurred before start

Real-World Example

A backend monitoring service tracks how long each API endpoint takes to respond and flags any request that exceeds that endpoint's configured SLA threshold. Comparing a measured response time against a threshold is exactly the kind of check Duration's Comparable implementation was built for — no manual unit conversion, no risk of comparing milliseconds against a threshold configured in a different unit.

1// File: EndpointSla.java 2import java.time.*; 3 4public record EndpointSla(String endpointName, Duration maxAllowedDuration) {}
1// File: SlaCheckResult.java 2import java.time.*; 3 4public record SlaCheckResult(String endpointName, Duration actualDuration, boolean withinSla) {}
1// File: ResponseTimeMonitor.java 2import java.time.*; 3 4public class ResponseTimeMonitor { 5 6 public SlaCheckResult checkResponse(EndpointSla sla, Instant requestStart, Instant requestEnd) { 7 Duration actualDuration = Duration.between(requestStart, requestEnd); 8 boolean withinSla = actualDuration.compareTo(sla.maxAllowedDuration()) <= 0; 9 10 return new SlaCheckResult(sla.endpointName(), actualDuration, withinSla); 11 } 12}
1// File: SlaMonitorDemo.java 2import java.time.*; 3 4public class SlaMonitorDemo { 5 public static void main(String[] args) { 6 EndpointSla checkoutSla = new EndpointSla("/api/checkout", Duration.ofMillis(500)); 7 8 ResponseTimeMonitor monitor = new ResponseTimeMonitor(); 9 10 Instant fastRequestStart = Instant.parse("2026-08-25T09:00:00.000Z"); 11 Instant fastRequestEnd = Instant.parse("2026-08-25T09:00:00.320Z"); 12 13 Instant slowRequestStart = Instant.parse("2026-08-25T09:05:00.000Z"); 14 Instant slowRequestEnd = Instant.parse("2026-08-25T09:05:00.780Z"); 15 16 SlaCheckResult fastResult = monitor.checkResponse(checkoutSla, fastRequestStart, fastRequestEnd); 17 SlaCheckResult slowResult = monitor.checkResponse(checkoutSla, slowRequestStart, slowRequestEnd); 18 19 System.out.println(fastResult.endpointName() + " took " + fastResult.actualDuration().toMillis() 20 + "ms, within SLA: " + fastResult.withinSla()); 21 System.out.println(slowResult.endpointName() + " took " + slowResult.actualDuration().toMillis() 22 + "ms, within SLA: " + slowResult.withinSla()); 23 } 24}
Output:
/api/checkout took 320ms, within SLA: true
/api/checkout took 780ms, within SLA: false

A mistake that appears often in fresher pull requests is comparing response times as raw long millisecond values pulled from timestamp differences, which compiles and runs fine right up until someone on the team accidentally compares a value measured in milliseconds against a threshold configured in seconds. Duration.compareTo() against a Duration-typed SLA threshold makes that entire category of unit-mismatch bug impossible to introduce in the first place.

Combining Duration With Other Features

Duration.between() works with any Temporal that supports time-based measurement — Instant, LocalTime, LocalDateTime, ZonedDateTime — each covered elsewhere in this series. Unlike Period, Duration implements Comparable<Duration> directly, which is exactly what makes SLA-style threshold checks possible with a single compareTo() call instead of an awkward workaround. Instant.plus(Duration) and Instant.minus(Duration) are the standard way to compute a deadline or an expiry moment from a fixed, precise span, distinct from LocalDate.plus(Period), which computes a calendar-relative one instead.

Best Practices

Use Duration for anything measured or configured in fixed units — timeouts, SLA thresholds, retry delays, cache expiry windows — and reach for Period only when the amount is genuinely calendar-relative.

Compare Duration values with compareTo(), isZero(), or isNegative() rather than converting both sides to a raw long and comparing manually, since that reintroduces exactly the unit-mismatch risk Duration exists to eliminate.

Store Duration as configuration wherever an application needs a tunable timeout or threshold, rather than a raw number whose unit lives only in a comment or a variable name that someone eventually stops trusting.

Reach for toMillis() or toNanos() only at the boundary where an older API genuinely requires a raw long, and keep every internal representation as a real Duration otherwise.

Common Mistakes

Assuming Duration.ofDays(1) behaves like a calendar day overlooks that it is defined as exactly 24 hours, while a calendar day can be 23 or 25 hours long across a daylight saving transition.

1// File: DurationVsPeriodDstMistake.java 2import java.time.*; 3 4public class DurationVsPeriodDstMistake { 5 public static void main(String[] args) { 6 // March 8, 2026 is the day US clocks spring forward for daylight 7 // saving time - that specific calendar day only has 23 real hours 8 ZonedDateTime start = ZonedDateTime.of(2026, 3, 8, 0, 0, 0, 0, ZoneId.of("America/New_York")); 9 10 ZonedDateTime plusOneCalendarDay = start.plus(Period.ofDays(1)); 11 ZonedDateTime plusOneExactDuration = start.plus(Duration.ofDays(1)); 12 13 System.out.println("Start: " + start); 14 System.out.println("Plus Period.ofDays(1): " + plusOneCalendarDay); 15 System.out.println("Plus Duration.ofDays(1): " + plusOneExactDuration); 16 } 17}
Output:
Start: 2026-03-08T00:00-05:00[America/New_York]
Plus Period.ofDays(1): 2026-03-09T00:00-04:00[America/New_York]
Plus Duration.ofDays(1): 2026-03-09T01:00-04:00[America/New_York]

Period.ofDays(1) lands on midnight the next calendar day, exactly matching the wall clock. Duration.ofDays(1) adds a genuine 24 hours to the underlying instant, and because March 8th only had 23 real hours that year, the result lands a full hour later — 1:00 AM instead of midnight.

Assuming Duration behaves like Period, where two values with the same total length can still be unequal, is a natural but incorrect over-generalization once you already know Period's surprising equals() behavior.

1// File: DurationNormalizationContrastMistake.java 2import java.time.*; 3import java.time.temporal.*; 4 5public class DurationNormalizationContrastMistake { 6 public static void main(String[] args) { 7 Duration fromMinutes = Duration.ofMinutes(90); 8 Duration fromSeconds = Duration.ofSeconds(5400); 9 Duration fromUnit = Duration.of(90, ChronoUnit.MINUTES); 10 11 // Unlike Period, Duration always normalizes to a single internal 12 // seconds-plus-nanos representation, so equals() correctly reports 13 // true for any two Durations covering the same total span 14 System.out.println("fromMinutes.equals(fromSeconds): " + fromMinutes.equals(fromSeconds)); 15 System.out.println("fromMinutes.equals(fromUnit): " + fromMinutes.equals(fromUnit)); 16 } 17}
Output:
fromMinutes.equals(fromSeconds): true
fromMinutes.equals(fromUnit): true

Trying to measure a gap between two LocalDate values with Duration.between() compiles fine, since LocalDate implements Temporal, but fails at runtime because LocalDate has no time-based unit for Duration to measure.

1// File: DurationBetweenLocalDatesMistake.java 2import java.time.*; 3import java.time.temporal.*; 4 5public class DurationBetweenLocalDatesMistake { 6 public static void main(String[] args) { 7 LocalDate start = LocalDate.of(2026, 1, 1); 8 LocalDate end = LocalDate.of(2026, 6, 1); 9 10 try { 11 Duration.between(start, end); 12 } catch (UnsupportedTemporalTypeException e) { 13 System.out.println("UnsupportedTemporalTypeException - LocalDate has no time component for Duration to measure"); 14 } 15 16 // Period.between() is the correct choice for two LocalDate values 17 Period gap = Period.between(start, end); 18 System.out.println("Period.between() works correctly: " + gap); 19 } 20}
Output:
UnsupportedTemporalTypeException - LocalDate has no time component for Duration to measure
Period.between() works correctly: P5M

Interview Questions

Q1. What is Duration, and how is it different from Period?

Duration represents a precise, time-based span measured in seconds and nanoseconds, with no concept of calendar units at all. Period represents a calendar-relative amount measured in years, months, and days, where the actual elapsed time varies depending on which dates it gets applied to. Duration.ofDays(1) is always exactly 24 hours; Period.ofDays(1) applied to a date lands on the same wall-clock time the next calendar day, which can occasionally be a different number of real hours away.

Q2. Does Duration implement Comparable? Why does that matter compared to Period?

Yes, Duration implements Comparable<Duration>, because a second has a fixed, unambiguous length regardless of context, making any two durations directly comparable. Period does not implement Comparable, because a month can be anywhere from 28 to 31 days, so "which is longer, one month or 31 days" has no single correct answer without applying both to an actual date. This distinction is a common follow-up once a candidate demonstrates knowing both classes exist.

Q3. Is Duration.ofDays(1) always exactly the same as a calendar day?

No. Duration.ofDays(1) is always exactly 24 hours, full stop. A calendar day can be 23 or 25 hours long on the specific day daylight saving time starts or ends in a zone that observes it, which is exactly why adding a Duration of one day to a ZonedDateTime can land at a different wall-clock time than adding a Period of one day would, as shown by the DST transition example in this article.

Q4. Why does Duration.between() throw an exception when called with two LocalDate values?

The call compiles because LocalDate implements the Temporal interface Duration.between() accepts, but it fails at runtime with UnsupportedTemporalTypeException, because LocalDate has no time-based unit like seconds for Duration to measure against — it is a date-only type. Period.between() is the correct method for measuring a gap between two LocalDate values.

Q5. Are two Duration objects built from different units, like ofMinutes(90) and ofSeconds(5400), considered equal?

Yes. Duration always normalizes into the same internal representation — total seconds plus a nanosecond adjustment — regardless of which factory method or unit was used to construct it, so equals() correctly reports true for any two Duration values covering the same total span. This is a deliberate contrast with Period, whose equals() compares raw stored fields and can report false for two periods with the same total length.

Q6. How would you compare a measured elapsed time against a configured SLA threshold?

Represent both the measurement and the threshold as Duration values, then call actualDuration.compareTo(thresholdDuration) <= 0 to check whether the measurement stayed within the limit. Keeping both sides as Duration rather than raw numbers eliminates the risk of comparing values measured in different units by mistake, exactly the pattern the SLA monitoring example in this article demonstrates.

FAQs

Can Duration represent a negative span of time?

Yes. Duration.between(start, end) returns a negative duration whenever end occurs before start, and isNegative() checks for exactly this case.

What is the difference between Duration.ofDays(1) applied to a ZonedDateTime and Period.ofDays(1) applied to the same value?

Duration.ofDays(1) adds exactly 24 real hours to the underlying instant, which can land at a different wall-clock time when the span crosses a daylight saving transition. Period.ofDays(1) advances the calendar date by one day and keeps the same wall-clock time, resolving to whatever offset applies on the new date. The two agree on ordinary days and differ specifically on the days daylight saving time starts or ends.

Does Duration support nanosecond precision?

Yes. Duration stores a nanosecond adjustment alongside its whole-second count, giving it precision down to a single nanosecond, though most everyday use only ever works with seconds or milliseconds.

Can Duration be added to a LocalDate?

No. LocalDate has no time-based fields for a Duration to apply to, so LocalDate.plus(Duration) does not exist as a method at all — Period is the correct type to add to a LocalDate.

What is Duration.ZERO?

A public static constant representing a duration of exactly zero seconds and zero nanoseconds, useful as a default value or a sentinel, and Duration.ZERO.isZero() always returns true.

How do I convert a Duration into a raw number of milliseconds for an older API?

Call toMillis(), which returns the total duration as a long number of milliseconds, or toNanos() for nanosecond precision. Both exist specifically as a bridge to older APIs that still expect a raw numeric time span rather than a Duration object.

Is Duration thread-safe?

Yes. Like every class in java.time, Duration is immutable, so a single instance can be shared freely across threads with no synchronization required, since nothing about it can ever change after it is created.

Summary

Duration gives a precise, fixed span of time its own type — seconds and nanoseconds, nothing calendar-relative about it — and because that span never changes meaning depending on context, Duration is the one java.time class among Period and its siblings that can be meaningfully compared directly with compareTo(). of() and its variants build one; plus(), minus(), and multipliedBy() combine them; and toHoursPart(), toMinutesPart(), and toSecondsPart() break one back down for display.

The habit worth carrying forward is reaching for Duration the moment a rule is genuinely fixed — a timeout, an SLA, a retry delay — and remembering that Duration.ofDays(1) is always 24 real hours, which is not always the same thing as "the next calendar day" once daylight saving time enters the picture. That single distinction, demonstrated concretely in this article's DST example, is exactly what separates Duration from Period in practice.

What to Read Next