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

  1. Swap or correct the argument order so endTS >= startTS at the call site.
  2. Validate/normalize both timestamps (epoch millis, not seconds) before calling; multiply second-epoch values by 1000L.
  3. 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

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


AI-assisted analysis of apache/skywalking@102af09b4a (2026-08-14). Data as JSON: /api/errors/0627fe147b153bae. Report an issue: GitHub.