pinpoint-apm/pinpoint · error · IllegalStateException

startTime not recorded

Error message

startTime not recorded

What it means

SpanEvent tracks its own lifecycle with startTime and afterTime; elapsedTime is computed as afterTime - startTime when the event completes. checkStartTime() enforces the invariant that markAfterTime/setAfterTime can only run after markStartTime recorded a non-zero startTime. If the event ended without ever being started, the state machine is broken, so an IllegalStateException is thrown rather than recording a bogus elapsed time.

Source

Thrown at agent-module/profiler/src/main/java/com/navercorp/pinpoint/profiler/context/SpanEvent.java:110

    public long getStartTime() {
        return startTime;
    }

    public void markAfterTime() {
        checkStartTime();
        setAfterTime(System.currentTimeMillis());
    }


    public void setAfterTime(long afterTime) {
        checkStartTime();
        this.elapsedTime = (int) (afterTime - startTime);
    }

    private void checkStartTime() {
        if (startTime == 0) {
            throw new IllegalStateException("startTime not recorded");
        }
    }

    public long getAfterTime() {
        return startTime + elapsedTime;
    }

    public int getStackId() {
        return stackId;
    }

    public void setStackId(int stackId) {
        this.stackId = stackId;
    }

    public boolean isTimeRecording() {
        return timeRecording;
    }

View on GitHub (pinned to 744c3d3075)

Solutions

  1. Ensure the interceptor/async flow always calls markStartTime() (or the before-phase) before markAfterTime()/setAfterTime().
  2. Guard the after-phase: only call markAfterTime if the event is still in the expected state (check currentSpanEvent/depth consistency).
  3. Check plugin interceptor logic for early returns or exceptions in before() that skip start-time recording.
  4. If timing is genuinely unknown, avoid setAfterTime and construct the event via APIs that record both endpoints.
  5. Reproduce with a minimal interceptor and log trace.getTraceId() + spanEvent depth to find which call path skips markStartTime.

Example fix

// before
spanEvent.markAfterTime(); // throws if never started
// after
if (spanEvent.getStartTime() != 0) {
    spanEvent.markAfterTime();
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (spanEvent.getStartTime() != 0) {
    spanEvent.markAfterTime();
} else {
    // start time missing; skip or log the lifecycle bug
}

Type guard

boolean isSpanEventStarted(SpanEvent e) {
    return e != null && e.getStartTime() != 0;
}

Try / catch

try {
    spanEvent.markAfterTime();
} catch (IllegalStateException e) {
    logger.warn("SpanEvent ended without startTime; interceptor before-phase likely skipped", e);
}

Prevention

When it happens

Trigger: Calling spanEvent.markAfterTime() or setAfterTime(...) on a SpanEvent whose markStartTime() was never invoked (or whose startTime was lost, e.g. the event was constructed but begin() flow was skipped) — typically when an interceptor's after() runs without its matching before() storing the start time.

Common situations: Custom plugins/interceptors that only implement the 'after' side of instrumentation or short-circuit before(); recursive/reentrant calls where a nested event overwrote state; exceptions in before() leaving the event half-initialized; SpanEvents serialized across async boundaries without copying startTime.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of pinpoint-apm/pinpoint@744c3d3075 (2026-09-07). Data as JSON: /api/errors/3594af197e524436. Report an issue: GitHub.