From a4313d5748fa99bad6f1198e9b8f4a4e02babbc2 Mon Sep 17 00:00:00 2001 From: Vcode2407 Date: Mon, 9 Nov 2026 19:11:59 +0530 Subject: [PATCH] Preserve repeated tracestate header fields Signed-off-by: Vcode2407 --- .../W3CTraceContextPropagator.java | 18 ++++++++-- .../W3CTraceContextPropagatorTest.java | 33 +++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/propagation/W3CTraceContextPropagator.java b/api/all/src/main/java/io/opentelemetry/api/trace/propagation/W3CTraceContextPropagator.java index 393439518df..f37aaf0f19a 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/propagation/W3CTraceContextPropagator.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/propagation/W3CTraceContextPropagator.java @@ -25,6 +25,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.logging.Logger; @@ -165,8 +166,21 @@ private static SpanContext extractImpl(@Nullable C carrier, TextMapGetter return contextFromParentHeader; } - String traceStateHeader = getter.get(carrier, TRACE_STATE); - if (traceStateHeader == null || traceStateHeader.isEmpty()) { + Iterator traceStateHeaders = getter.getAll(carrier, TRACE_STATE); + if (traceStateHeaders == null || !traceStateHeaders.hasNext()) { + return contextFromParentHeader; + } + + String traceStateHeader = traceStateHeaders.next(); + if (traceStateHeaders.hasNext()) { + // W3C Trace Context requires combining repeated fields in the order received. + StringBuilder combined = new StringBuilder(traceStateHeader); + while (traceStateHeaders.hasNext()) { + combined.append(',').append(traceStateHeaders.next()); + } + traceStateHeader = combined.toString(); + } + if (traceStateHeader.isEmpty()) { return contextFromParentHeader; } diff --git a/api/all/src/test/java/io/opentelemetry/api/trace/propagation/W3CTraceContextPropagatorTest.java b/api/all/src/test/java/io/opentelemetry/api/trace/propagation/W3CTraceContextPropagatorTest.java index 851d278e73f..87429cc737a 100644 --- a/api/all/src/test/java/io/opentelemetry/api/trace/propagation/W3CTraceContextPropagatorTest.java +++ b/api/all/src/test/java/io/opentelemetry/api/trace/propagation/W3CTraceContextPropagatorTest.java @@ -21,7 +21,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.stream.Stream; import javax.annotation.Nullable; @@ -249,6 +251,37 @@ void extract_SampledContext_WithTraceState() { TRACE_ID_BASE16, SPAN_ID_BASE16, TraceFlags.getSampled(), TRACE_STATE)); } + @Test + void extract_multipleTraceStateHeaders() { + Map> carrier = new HashMap<>(); + carrier.put( + W3CTraceContextPropagator.TRACE_PARENT, + Collections.singletonList(TRACEPARENT_HEADER_SAMPLED)); + carrier.put(W3CTraceContextPropagator.TRACE_STATE, Arrays.asList("bar=baz", "foo=bar")); + TextMapGetter>> multiGetter = + new TextMapGetter>>() { + @Override + @Nullable + public String get(Map> headers, String key) { + return headers.getOrDefault(key, Collections.emptyList()).stream() + .findFirst() + .orElse(null); + } + + @Override + public Iterator getAll(Map> headers, String key) { + return headers.getOrDefault(key, Collections.emptyList()).iterator(); + } + }; + + Context context = w3cTraceContextPropagator.extract(Context.root(), carrier, multiGetter); + + assertThat(getSpanContext(context)) + .isEqualTo( + SpanContext.createFromRemoteParent( + TRACE_ID_BASE16, SPAN_ID_BASE16, TraceFlags.getSampled(), TRACE_STATE)); + } + @Test void extract_NotSampledContext_WithTraceState() { Map carrier = new LinkedHashMap<>();