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
- Ensure the interceptor/async flow always calls markStartTime() (or the before-phase) before markAfterTime()/setAfterTime().
- Guard the after-phase: only call markAfterTime if the event is still in the expected state (check currentSpanEvent/depth consistency).
- Check plugin interceptor logic for early returns or exceptions in before() that skip start-time recording.
- If timing is genuinely unknown, avoid setAfterTime and construct the event via APIs that record both endpoints.
- 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
- Pair every interceptor before() with a guaranteed after() (use try/finally)
- Never early-return from before() without recording start time
- Test plugins with exceptions thrown in the before phase
- Verify depth/currentSpanEvent consistency in nested/async flows
- Log span event lifecycle in staging to catch missing start calls early
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
- UNSUPPORTED_OPERATION
- Cannot leave with scope. depth: ${depth}
- Failed to detect pinpoint profile. Please add -Dpinpoint.act
- unsupported profile or profile alias:
- slot type:${slot}
AI-assisted analysis of pinpoint-apm/pinpoint@744c3d3075 (2026-09-07).
Data as JSON: /api/errors/3594af197e524436.
Report an issue: GitHub.