apache/skywalking · error · IllegalArgumentException
End time must not be before start
Error message
End time must not be before start
What it means
DurationUtils.timestamp2Duration() converts a pair of epoch-millisecond timestamps into a Duration object (auto-selecting MINUTE/HOUR/DAY step by span). It rejects inverted input — endTS < startTS — with IllegalArgumentException, since a negative span cannot be represented. This guards the query API against nonsensical time windows before they hit storage.
Source
Thrown at oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/query/DurationUtils.java:229
YYYY_MM_DD.parseDateTime(dateStr);
return;
case HOUR:
YYYY_MM_DD_HH.parseDateTime(dateStr);
return;
case MINUTE:
YYYY_MM_DD_HHMM.parseDateTime(dateStr);
return;
case SECOND:
YYYY_MM_DD_HHMMSS.parseDateTime(dateStr);
return;
}
throw new UnexpectedException("Unsupported step " + step.name());
}
public static Duration timestamp2Duration(long startTS, long endTS) {
Duration duration = new Duration();
if (endTS < startTS) {
throw new IllegalArgumentException("End time must not be before start");
}
DateTime startDT = new DateTime(startTS);
DateTime endDT = new DateTime(endTS);
long durationValue = endTS - startTS;
if (durationValue <= 3600000) {
duration.setStep(Step.MINUTE);
duration.setStart(startDT.toString(DurationUtils.YYYY_MM_DD_HHMM));
duration.setEnd(endDT.toString(DurationUtils.YYYY_MM_DD_HHMM));
} else if (durationValue <= 86400000) {
duration.setStep(Step.HOUR);
duration.setStart(startDT.toString(DurationUtils.YYYY_MM_DD_HH));
duration.setEnd(endDT.toString(DurationUtils.YYYY_MM_DD_HH));
} else {
duration.setStep(Step.DAY);
duration.setStart(startDT.toString(DurationUtils.YYYY_MM_DD));
duration.setEnd(endDT.toString(DurationUtils.YYYY_MM_DD));View on GitHub (pinned to 102af09b4a)
Solutions
- Swap or correct the argument order so endTS >= startTS at the call site.
- Validate/normalize both timestamps (epoch millis, not seconds) before calling; multiply second-epoch values by 1000L.
- Default a missing end to now() rather than 0.
Example fix
// before Duration d = DurationUtils.timestamp2Duration(endTS, startTS); // after Duration d = DurationUtils.timestamp2Duration(startTS, endTS);
Defensive patterns
Strategy: validation
Validate before calling
if (endTS < startTS) throw new IllegalArgumentException("end must be >= start");
Duration d = DurationUtils.timestamp2Duration(startTS, endTS); Type guard
boolean isValidWindow(long startTS, long endTS) { return endTS >= startTS && startTS > 0 && endTS <= System.currentTimeMillis() + 86_400_000L; } Try / catch
try { Duration d = DurationUtils.timestamp2Duration(startTS, endTS); } catch (IllegalArgumentException e) { if ("End time must not be before start".equals(e.getMessage())) { long t = startTS; startTS = endTS; endTS = t; /* or surface a form error */ } else throw e; } Prevention
- Name variables explicitly (startTS/endTS) — argument-order swaps are the top cause.
- Assert epoch units (millis) at API boundaries; reject second-epoch values.
- Default missing end to now(), never 0.
When it happens
Trigger: Calling timestamp2Duration(startTS, endTS) (directly or via a query path that builds Duration from timestamps) where the end timestamp is numerically smaller than the start.
Common situations: Client code passing Date.now() as end and a constant as start in the wrong argument order; unit-test fixtures with hand-written inverted timestamps; timezone/millisecond-vs-second confusion making 'end' parse as an earlier instant; UI sending an empty end that defaults to 0.
Related errors
- Duration data error, the range between the start time and th
- time field is required when uuid is absent.
- queryAlarms entity is invalid (scope={scope}); required name
- Failed to load GenAI configuration file.
- Output type {outputTypeName} has no setter {setterName}() fo
AI-assisted analysis of apache/skywalking@102af09b4a (2026-08-14).
Data as JSON: /api/errors/0627fe147b153bae.
Report an issue: GitHub.