openzipkin/zipkin · error · IllegalStateException

Missing :

Error message

Missing :

What it means

Span.Builder.build() throws IllegalStateException('Missing : traceId') / ('Missing : id') when the span is built without the two mandatory fields. Unlike bad-format errors thrown at setter time, this is a completeness check at build time: traceId and id must both have been set (via any overload). The message concatenates every missing field, e.g. 'Missing : traceId id'.

Source

Thrown at zipkin/src/main/java/zipkin2/Span.java:590

        flags |= FLAG_SHARED;
      } else {
        flags &= ~FLAG_SHARED;
      }
      return this;
    }

    /** Sets {@link Span#shared} */
    public Builder shared(@Nullable Boolean shared) {
      if (shared != null) return shared((boolean) shared);
      flags &= ~(FLAG_SHARED_SET | FLAG_SHARED);
      return this;
    }

    public Span build() {
      String missing = "";
      if (traceId == null) missing += " traceId";
      if (id == null) missing += " id";
      if (!missing.isEmpty()) throw new IllegalStateException("Missing :" + missing);
      if (id.equals(parentId)) { // edge case, so don't require a logger field
        Logger logger = Logger.getLogger(Span.class.getName());
        if (logger.isLoggable(FINEST)) {
          logger.fine(format("undoing circular dependency: traceId=%s, spanId=%s", traceId, id));
        }
        parentId = null;
      }
      // shared is for the server side, unset it if accidentally set on the client side
      if ((flags & FLAG_SHARED) == FLAG_SHARED && kind == Kind.CLIENT) {
        Logger logger = Logger.getLogger(Span.class.getName());
        if (logger.isLoggable(FINEST)) {
          logger.fine(format("removing shared flag on client: traceId=%s, spanId=%s", traceId, id));
        }
        shared(null);
      }
      return new Span(this);
    }

View on GitHub (pinned to 878ce2a1fa)

Solutions

  1. Ensure every code path sets both .traceId(...) and .id(...) before build(); check the conditional branches that skip them.
  2. If a trace context may be absent, branch explicitly: either start a new trace (generate both IDs) or skip reporting — never build a partial span.
  3. Move build() after all setters in the method so no early return path can bypass ID assignment.

Example fix

// before
Span.Builder b = Span.newBuilder().id(spanId);
if (traceContext != null) b.traceId(traceContext.traceIdString());
return b.build(); // ISE when traceContext == null

// after
Span.Builder b = Span.newBuilder().id(spanId);
if (traceContext == null) return null; // or generate a new trace id
return b.traceId(traceContext.traceIdString()).build();
Defensive patterns

Strategy: validation

Validate before calling

// before build(): ensure mandatory fields
if (traceId == null || spanId == null) {
  throw new IllegalStateException("refusing to build incomplete span");
}

Try / catch

try {
  return builder.build();
} catch (IllegalStateException e) {
  if (e.getMessage().startsWith("Missing :")) { /* log + drop malformed span */ }
  else throw e;
}

Prevention

When it happens

Trigger: Building a span after setting only id (trace ID never set), only traceId, or neither — often when the code builds first and conditionally sets IDs afterwards, or when ID-setting code is skipped on an error path that still reaches build().

Common situations: Shared span-building helper where some callers pass a nullable trace context and the helper skips the setter on null; async/reporter code that rebuilds partial spans for error reporting; refactoring that moved traceId() out of a base method.

Related errors


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