From 0cc0c3b388e39b67ef5d523e95fc93d39f1c5c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Thu, 18 Jun 2026 20:12:47 +0000 Subject: [PATCH 1/2] EnvironmentGetter and EnvironmentSetter empty name normalization --- .../api/incubator/propagation/EnvironmentGetter.java | 8 ++++++-- .../api/incubator/propagation/EnvironmentSetter.java | 9 +++++++-- .../incubator/propagation/EnvironmentGetterTest.java | 10 ++++++++-- .../incubator/propagation/EnvironmentSetterTest.java | 3 +++ 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java index 5d379a6794b..84f5fb6c240 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java @@ -31,8 +31,12 @@ *

This getter automatically sanitizes keys to match environment variable naming conventions: * *

* *

Values are validated to contain only characters valid in HTTP header fields per + *

  • An empty key is replaced with a single underscore ({@code _}) *
  • ASCII letters are converted to uppercase *
  • Any character that is not an ASCII letter, digit, or underscore is replaced with an * underscore @@ -68,8 +69,8 @@ public void set(@Nullable Map carrier, String key, String value) /** * Determine if a key is a valid normalized environment variable name. Returns {@code true} if - * {@code key} is non-empty, contains only uppercase ASCII letters, digits, and underscores, and - * does not start with a digit. + * {@code key} is non-empty, contains only uppercase ASCII letters, digits, and underscores, and + * does not start with a digit. */ static boolean isNormalizedKey(String key) { if (key.isEmpty()) { @@ -92,6 +93,7 @@ static boolean isNormalizedKey(String key) { * Normalizes a key to be a valid environment variable name. * *
      + *
    • An empty key is replaced with a single underscore ({@code _}) *
    • ASCII letters are converted to uppercase *
    • Any character that is not an ASCII letter, digit, or underscore is replaced with an * underscore (including {@code .}, {@code -}, whitespace, and control characters) @@ -102,6 +104,9 @@ static String normalizeKey(String key) { if (isNormalizedKey(key)) { return key; } + if (key.isEmpty()) { + return "_"; + } StringBuilder sb = new StringBuilder(key.length() + 1); for (int i = 0; i < key.length(); i++) { char ch = key.charAt(i); diff --git a/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetterTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetterTest.java index 7a2948e0eda..9336d8ea8dd 100644 --- a/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetterTest.java +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetterTest.java @@ -40,12 +40,15 @@ void get_normalization() { // Carrier entries are expected to have normalized keys (i.e. set via EnvironmentSetter). carrier.put("OTEL_TRACE_ID", "val1"); carrier.put("OTEL_BAGGAGE_KEY", "val2"); + carrier.put("_", "val3"); // Carrier entry with an unnormalized key is not reachable via get(). - carrier.put("otel-unreachable-key", "val3"); + carrier.put("", "val4"); + carrier.put("otel-unreachable-key", "val5"); // Lookup keys are normalized before the carrier map is consulted. assertThat(EnvironmentGetter.getInstance().get(carrier, "otel.trace.id")).isEqualTo("val1"); assertThat(EnvironmentGetter.getInstance().get(carrier, "otel-baggage-key")).isEqualTo("val2"); + assertThat(EnvironmentGetter.getInstance().get(carrier, "")).isEqualTo("val3"); // Carrier entries with unnormalized keys are not enumerated. assertThat(EnvironmentGetter.getInstance().get(carrier, "otel-unreachable-key")).isNull(); } @@ -60,12 +63,15 @@ void get_null() { @SuppressLogger(EnvironmentGetter.class) void keys_onlyNormalizedKeysIncluded() { Map carrier = new HashMap<>(); + carrier.put("", "V0"); carrier.put("otel.trace.id", "V1"); carrier.put("otel-baggage-key", "V2"); carrier.put("OTEL_FOO", "V3"); + carrier.put("_", "V4"); // Non-normalized keys are excluded; only already-normalized keys are returned. - assertThat(EnvironmentGetter.getInstance().keys(carrier)).containsExactlyInAnyOrder("OTEL_FOO"); + assertThat(EnvironmentGetter.getInstance().keys(carrier)) + .containsExactlyInAnyOrder("OTEL_FOO", "_"); for (String key : EnvironmentGetter.getInstance().keys(carrier)) { assertThat(EnvironmentGetter.getInstance().get(carrier, key)).isNotNull(); } diff --git a/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetterTest.java b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetterTest.java index f0d2b2e73ee..e4c40b8a282 100644 --- a/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetterTest.java +++ b/api/incubator/src/test/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetterTest.java @@ -34,9 +34,11 @@ void set_sanitization() { Map carrier = new HashMap<>(); EnvironmentSetter.getInstance().set(carrier, "otel.trace.id", "val1"); EnvironmentSetter.getInstance().set(carrier, "otel-baggage-key", "val2"); + EnvironmentSetter.getInstance().set(carrier, "", "val3"); assertThat(carrier).containsEntry("OTEL_TRACE_ID", "val1"); assertThat(carrier).containsEntry("OTEL_BAGGAGE_KEY", "val2"); + assertThat(carrier).containsEntry("_", "val3"); } @Test @@ -76,6 +78,7 @@ static Stream isNormalizedKeyCases() { return Stream.of( Arguments.argumentSet("uppercase letters", "TRACEPARENT", true), Arguments.argumentSet("uppercase with underscores", "OTEL_TRACE_ID", true), + Arguments.argumentSet("single underscore", "_", true), Arguments.argumentSet("single letter", "A", true), Arguments.argumentSet("letter then digit", "A0", true), Arguments.argumentSet("mixed uppercase digits underscores", "A_B_0", true), From d8b7ed81951abf1c8fc5c5e348cf1340697e9746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Thu, 18 Jun 2026 20:22:35 +0000 Subject: [PATCH 2/2] fmt --- .../api/incubator/propagation/EnvironmentGetter.java | 3 +-- .../api/incubator/propagation/EnvironmentSetter.java | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java index 84f5fb6c240..204bc74cfc7 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentGetter.java @@ -32,8 +32,7 @@ * *
        *
      • Replaces an empty key with a single underscore ({@code _}) - *
      • Converts ASCII letters to uppercase (e.g., {@code traceparent} becomes {@code - * TRACEPARENT}) + *
      • Converts ASCII letters to uppercase (e.g., {@code traceparent} becomes {@code TRACEPARENT}) *
      • Replaces every character that is not an ASCII letter, digit, or underscore with an * underscore *
      • Prepends an underscore if the result would otherwise start with an ASCII digit diff --git a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java index a619ad0d38c..d56740e515a 100644 --- a/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java +++ b/api/incubator/src/main/java/io/opentelemetry/api/incubator/propagation/EnvironmentSetter.java @@ -69,8 +69,8 @@ public void set(@Nullable Map carrier, String key, String value) /** * Determine if a key is a valid normalized environment variable name. Returns {@code true} if - * {@code key} is non-empty, contains only uppercase ASCII letters, digits, and underscores, and - * does not start with a digit. + * {@code key} is non-empty, contains only uppercase ASCII letters, digits, and underscores, and + * does not start with a digit. */ static boolean isNormalizedKey(String key) { if (key.isEmpty()) {