jaegertracing/jaeger · error
cannot unmarshal OTLP : %w
Error message
cannot unmarshal OTLP : %w
What it means
otlp2traces decodes raw OTLP JSON bytes into OpenTelemetry ptrace.Traces before converting them to Jaeger model.Trace objects. When ptrace.JSONUnmarshaler.UnmarshalTraces cannot parse the payload (malformed JSON or an OTLP shape it does not accept), the error is wrapped as "cannot unmarshal OTLP : %w" so the underlying decoder reason is preserved. Called via transformOTLP, this is the entry point for OTLP-format input to the query extension.
Source
Thrown at cmd/jaeger/internal/extension/jaegerquery/internal/otlp_translator.go:19
// Copyright (c) 2024 The Jaeger Authors.
// SPDX-License-Identifier: Apache-2.0
package app
import (
"fmt"
"go.opentelemetry.io/collector/pdata/ptrace"
"github.com/jaegertracing/jaeger-idl/model/v1"
"github.com/jaegertracing/jaeger/internal/storage/v2/v1adapter"
)
func otlp2traces(otlpSpans []byte) ([]*model.Trace, error) {
ptraceUnmarshaler := ptrace.JSONUnmarshaler{}
otlpTraces, err := ptraceUnmarshaler.UnmarshalTraces(otlpSpans)
if err != nil {
return nil, fmt.Errorf("cannot unmarshal OTLP : %w", err)
}
jaegerBatches := v1adapter.V1BatchesFromTraces(otlpTraces)
var traces []*model.Trace
traceMap := make(map[model.TraceID]*model.Trace)
for _, batch := range jaegerBatches {
for _, span := range batch.Spans {
if span.Process == nil {
span.Process = batch.Process
}
trace, ok := traceMap[span.TraceID]
if !ok {
newtrace := model.Trace{
Spans: []*model.Span{span},
}
traceMap[span.TraceID] = &newtrace
traces = append(traces, &newtrace)
} else {
trace.Spans = append(trace.Spans, span)View on GitHub (pinned to 806f444784)
Solutions
- Inspect the wrapped inner error (%w) to identify the exact JSON/OTLP decode failure and fix the payload accordingly.
- Validate the payload is real OTLP JSON (ExportTraceServiceRequest shape, hex-encoded traceId/spanId, proper resourceSpans/scopeSpans nesting) before sending.
- If sending Jaeger-format JSON, send it to the Jaeger-native endpoint instead of the OTLP one.
- Check that client and server OTLP schema versions are compatible; regenerate payloads from a current SDK/exporter.
Example fix
// before: sending Jaeger-style JSON to the OTLP translator
POST body: {"data":[{"traceID":"abc","spans":[...]}]}
// after: correct OTLP JSON shape
{"resourceSpans":[{"resource":{"attributes":[...]},"scopeSpans":[{"spans":[{"traceId":"0af7651916cd43dd8448eb211c80319c","spanId":"b7ad6b7169203331","name":"op","kind":"SPAN_KIND_SERVER"}]}]}]} Defensive patterns
Strategy: validation
Validate before calling
var buf map[string]any
if err := json.Unmarshal(otlpSpans, &buf); err != nil {
return fmt.Errorf("payload is not valid JSON: %w", err)
}
if _, ok := buf["resourceSpans"]; !ok {
return errors.New("payload lacks resourceSpans; not an OTLP ExportTraceServiceRequest")
} Type guard
func isOTLPJSON(body []byte) bool {
var m map[string]json.RawMessage
return json.Unmarshal(body, &m) == nil && m["resourceSpans"] != nil
} Try / catch
traces, err := otlp2traces(otlpSpans)
if err != nil {
var syntaxErr *json.SyntaxError
if errors.As(err, &syntaxErr) {
return fmt.Errorf("malformed OTLP JSON at offset %d: %w", syntaxErr.Offset, err)
}
return fmt.Errorf("OTLP decode rejected: %w", err)
} Prevention
- Send payloads only from an OTel SDK/OTLP exporter in JSON mode; never hand-craft OTLP JSON.
- Validate with isOTLPJSON or a schema check before posting to OTLP endpoints.
- Log the first ~200 bytes of a failing body to catch truncation or wrong-format posts early.
When it happens
Trigger: transformOTLP -> otlp2traces is invoked with a []byte payload that fails ptrace.JSONUnmarshaler.UnmarshalTraces: syntactically invalid JSON, JSON that is not an OTLP ExportTraceServiceRequest-compatible structure, missing required fields such as resourceSpans, or wrong field types.
Common situations: A client posts a Jaeger JSON payload (or arbitrary JSON) to an endpoint that expects OTLP JSON; a span field has the wrong type (e.g. traceId as integer instead of hex string); empty or truncated request bodies; version drift where the payload uses an OTLP field name the embedded OTel collector version does not recognize.
Related errors
- unmarshalling ElasticSearch documents failed
- failed writing HTTP response: %w
- failed marshalling HTTP response to JSON: %w
- unsupported span kind: '%s'
- invalid OTLP proxy target %q: %w
AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01).
Data as JSON: /api/errors/22d992d22d6d34fd.
Report an issue: GitHub.