Skip to content

AttributeMap does not respect last-value-wins semantics for duplicate keys (SpanBuilder.setAttribute vs Attributes.builder) #7897

@Sabaev

Description

@Sabaev

Describe the bug
According to the OpenTelemetry specification:

However, in OpenTelemetry Java 1.56.0, last-value-wins semantics are not consistently applied.
Attributes.builder().put("key", ...) behaves correctly, but SpanBuilder.setAttribute("key", ...) produces different observable results when the same key name is reused with different types.

The core issue:
AttributeMap does not treat duplicate string keys consistently, violating spec last-value-wins semantics.

Steps to reproduce
JUnit 5 test (Java):

import static org.junit.jupiter.api.Assertions.assertEquals;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;

class AttributeLastWinsTest {

  @Test
  void lastWinSemanticConvention() {
    InMemorySpanExporter exporter = InMemorySpanExporter.create();

    SdkTracerProvider tracerProvider =
        SdkTracerProvider.builder()
            .addSpanProcessor(SimpleSpanProcessor.create(exporter))
            .build();

    OpenTelemetrySdk telemetry =
        OpenTelemetrySdk.builder()
            .setTracerProvider(tracerProvider)
            .build();

    // Span 1: Attributes.builder() with same key, different types
    telemetry
        .getTracer("test")
        .spanBuilder("okSpan")
        .setAllAttributes(
            Attributes.builder()
                .put("key", "value")
                .put("key", false)
                .build())
        .startSpan()
        .end();

    // Span 2: SpanBuilder.setAttribute(...) with same key, different types
    telemetry
        .getTracer("test")
        .spanBuilder("test")
        .setAttribute("key", "value")
        .setAttribute("key", false)
        .startSpan()
        .end();

    List<SpanData> finished = exporter.getFinishedSpanItems();

    // Span 1: last-wins behavior (correct)
    assertEquals(
        List.of(false),
        new ArrayList<>(finished.get(0).getAttributes().asMap().values()));

    // Span 2: last-wins behavior is incorrect
    assertEquals(
        List.of(false),
        new ArrayList<>(finished.get(1).getAttributes().asMap().values()));
  }
}

What did you expect to see?
For both spans:

Only one attribute with key "key" should appear.

The final value should be the last assigned value: false.

What did you see instead?
Attributes.builder() → correct last-value-wins behavior.

SpanBuilder.setAttribute(String, ...) → incorrect last-value-wins behavior.

This contradicts the specification requirement that setting the same key SHOULD overwrite the existing value.

What version and what artifacts are you using?
Artifacts:
io.opentelemetry:opentelemetry-api
io.opentelemetry:opentelemetry-sdk
io.opentelemetry:opentelemetry-sdk-testing
org.junit.jupiter:junit-jupiter

Version: 1.56.0

dependencies {
    testImplementation("io.opentelemetry:opentelemetry-api:1.56.0")
    testImplementation("io.opentelemetry:opentelemetry-sdk:1.56.0")
    testImplementation("io.opentelemetry:opentelemetry-sdk-testing:1.56.0")
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}

Environment
Compiler: openjdk 17.0.8.1 2023-08-24
OS: MacOs 15.6.1

Tip: React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it. Learn more here.

Metadata

Metadata

Assignees

Labels

BugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions