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..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
@@ -31,8 +31,11 @@
*
This getter automatically sanitizes keys to match environment variable naming conventions:
*
*
- * - Converts keys to uppercase (e.g., {@code traceparent} becomes {@code TRACEPARENT})
- *
- Replaces {@code .} and {@code -} with underscores
+ *
- Replaces an empty key with a single underscore ({@code _})
+ *
- 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
*
*
* 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
@@ -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),