openzipkin/zipkin · error · IllegalArgumentException
empty trace ID
Error message
empty trace ID
What it means
Span.Builder.traceId(long high, long low) encodes a 64- or 128-bit trace ID from two longs and throws IllegalArgumentException('empty trace ID') when both high and low are zero. A trace ID of all zeros is invalid in the Zipkin/B3 data model because it cannot uniquely identify a trace. The guard runs before any hex encoding happens, so the builder state is left untouched.
Source
Thrown at zipkin/src/main/java/zipkin2/Span.java:405
}
/**
* Sets {@link Span#id()} or throws {@link IllegalArgumentException} if not lower-hex format.
*/
public Builder traceId(String traceId) {
this.traceId = normalizeTraceId(traceId);
return this;
}
/**
* Encodes 64 or 128 bits from the input into a hex trace ID.
*
* @param high Upper 64bits of the trace ID. Zero means the trace ID is 64-bit.
* @param low Lower 64bits of the trace ID.
* @throws IllegalArgumentException if both values are zero
*/
public Builder traceId(long high, long low) {
if (high == 0L && low == 0L) throw new IllegalArgumentException("empty trace ID");
char[] data = RecyclableBuffers.shortStringBuffer();
int pos = 0;
if (high != 0L) {
writeHexLong(data, pos, high);
pos += 16;
}
writeHexLong(data, pos, low);
this.traceId = new String(data, 0, high != 0L ? 32 : 16);
return this;
}
/** Hex encodes the input as the {@link Span#parentId()} or unsets if the input is zero. */
public Builder parentId(long parentId) {
this.parentId = parentId != 0L ? toLowerHex(parentId) : null;
return this;
}
/**View on GitHub (pinned to 878ce2a1fa)
Solutions
- Skip creating the span when both longs are zero: treat zero as 'no active trace' rather than passing it through.
- Trace the origin of the two longs (e.g. B3 single-header or traceparent parsing) and fix the upstream parser so a missing ID is surfaced as null/absent, not 0.
- If the ID arrives as a hex string, use Span.Builder.traceId(String) or Span.normalizeTraceId instead of converting to longs yourself.
Example fix
// before span = Span.newBuilder().traceId(high, low).id(spanId).build(); // after if (high == 0L && low == 0L) return null; // no active trace span = Span.newBuilder().traceId(high, low).id(spanId).build();
Defensive patterns
Strategy: validation
Validate before calling
boolean hasTraceId(long high, long low) {
return high != 0L || low != 0L;
}
// use: if (hasTraceId(high, low)) b.traceId(high, low); else startNewTrace(); Prevention
- Treat 0L/0L as 'no active trace' at extraction time; never forward zeros into the builder.
- Prefer the String overloads (traceId(String)) when IDs originate as hex text.
When it happens
Trigger: Calling Span.newBuilder().traceId(0L, 0L), or traceId(high, low) where both values came from an uninitialized long field, a defaulted primitive, or a parsed value that silently defaulted to 0 on error.
Common situations: Instrumentation code that reads trace IDs from headers/context objects that were missing (so the parsed long stayed 0), porting code from a tracer that represents 'no trace' as zero, or test fixtures generated with new Random() seeds of 0.
Related errors
AI-assisted analysis of openzipkin/zipkin@878ce2a1fa (2026-08-14).
Data as JSON: /api/errors/381d704d6ed1ce61.
Report an issue: GitHub.