openzipkin/zipkin · error · IllegalStateException

Already Executed

Error message

Already Executed

What it means

Call.Base.execute throws IllegalStateException('Already Executed') when execute() is invoked more than once on the same Call. Zipkin Calls are one-shot, modeled after OkHttp's Request: a Call represents a single invocation, and repeated execution would double-report or race the underlying resource. The executed flag is set under synchronization so the check is thread-safe.

Source

Thrown at zipkin/src/main/java/zipkin2/Call.java:376

    @Override public String toString() {
      return "ErrorHandling{call=" + delegate + ", errorHandler=" + errorHandler + "}";
    }

    @Override public Call<V> clone() {
      return new ErrorHandling<>(errorHandler, delegate.clone());
    }
  }

  public static abstract class Base<V> extends Call<V> {
    volatile boolean canceled;
    boolean executed;

    protected Base() {
    }

    @Override public final V execute() throws IOException {
      synchronized (this) {
        if (this.executed) throw new IllegalStateException("Already Executed");
        this.executed = true;
      }

      if (isCanceled()) {
        throw new IOException("Canceled");
      } else {
        return this.doExecute();
      }
    }

    protected abstract V doExecute() throws IOException;

    @Override public final void enqueue(Callback<V> callback) {
      synchronized (this) {
        if (this.executed) throw new IllegalStateException("Already Executed");
        this.executed = true;
      }

View on GitHub (pinned to 878ce2a1fa)

Solutions

  1. Create a fresh Call for each attempt: re-invoke storage.getTraces(...) (Calls are cheap factories).
  2. Use call.clone() if the implementation supports it to re-run an identical request.
  3. Audit code paths where execute() and enqueue() can both run on the same Call instance.

Example fix

// before
Call<List<Span>> call = storage.getTraces(query);
List<Span> first = call.execute();
List<Span> retry = call.execute(); // IllegalStateException

// after
List<List<Span>> first = storage.getTraces(query).execute();
List<List<Span>> retry = storage.getTraces(query).execute();
Defensive patterns

Strategy: validation

Validate before calling

// never reuse a Call: re-create it per attempt
Call<List<List<Span>>> attempt = storage.getTraces(query); // fresh each time

Try / catch

try { call.execute(); } catch (IllegalStateException e) { if ("Already Executed".equals(e.getMessage())) { /* logic bug: re-create the Call instead */ } throw e; }

Prevention

When it happens

Trigger: Holding a Call object (e.g. from storage.getTraces(...)) and calling execute() twice, or calling execute() after enqueue() on the same instance — enqueue also flips the executed flag.

Common situations: Retry loops that re-run the same Call instead of creating a new one; caching Call objects; helpers that both enqueue and execute depending on a flag; sharing a Call between threads.

Related errors


AI-assisted analysis of openzipkin/zipkin@878ce2a1fa (2026-08-14). Data as JSON: /api/errors/18f6b257efdd2559. Report an issue: GitHub.