Repository: helidon-io/helidon (java)

Issue:
NPE in io.opentelemetry.api.baggage.BaggageEntry.getValue() using alternatives to deprecated Span baggage calls
## Environment Details
* Helidon Version: 4.0.10 SE
* JDK version: 21
* OS: OL7
----------

## Problem Description
Changes in Helidon SE 4.0.5 io.helidon.tracing.Span deprecated longstanding baggage calls.
Specifically `String baggage(String key)`
But trying to use the alternatives in some cases cause NPE when baggage is untouched in the span.

## Steps to reproduce
See uploaded standalone test cases
[BaggageOptionalTest.java.txt](https://github.com/user-attachments/files/15820473/BaggageOptionalTest.java.txt)



Pull request:
4.0.11: After retrieval check baggage entry for null before dereferencing it

### Description

Backport of #8885 to 4.0.11.

Resolves #8882

Check the BaggageEntry retrieved from the map for null before attempting to get the entry's value to return.

Add test.

### Documentation

Bug fix; no doc impact.

Patch:
diff --git a/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/MutableOpenTelemetryBaggage.java b/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/MutableOpenTelemetryBaggage.java
index 52db8a3b13..e73286773d 100644
--- a/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/MutableOpenTelemetryBaggage.java
+++ b/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/MutableOpenTelemetryBaggage.java
@@ -96,7 +96,10 @@ class MutableOpenTelemetryBaggage implements Baggage, WritableBaggage {
 
     @Override
     public Optional<String> get(String key) {
-        return Optional.ofNullable(values.get(key).getValue());
+        BaggageEntry baggageEntry = values.get(key);
+        return baggageEntry == null
+                ? Optional.empty()
+                : Optional.ofNullable(baggageEntry.getValue());
     }
 
     @Override


Test patch:
diff --git a/tracing/provider-tests/src/main/java/io/helidon/tracing/providers/tests/BaggageOptionalTests.java b/tracing/provider-tests/src/main/java/io/helidon/tracing/providers/tests/BaggageOptionalTests.java
new file mode 100644
index 0000000000..a1038145bb
--- /dev/null
+++ b/tracing/provider-tests/src/main/java/io/helidon/tracing/providers/tests/BaggageOptionalTests.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.helidon.tracing.providers.tests;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+
+import io.helidon.common.testing.junit5.OptionalMatcher;
+import io.helidon.tracing.Span;
+import io.helidon.tracing.Tracer;
+
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+class BaggageOptionalTests {
+    // Demonstrate NPE in io.opentelemetry.api.baggage.BaggageEntry.getValue()
+    private static final String KEY = "baggageGetShouldCauseEmptyReturn_NotNPE";
+
+    @Test
+    void baggageRetrievalWhenNeverTouchedShouldBeEmptyFromSpan() {
+        runWithSpanHarness(span -> {
+            // NPE in 4.0.5 -> 4.0.10
+            final var actual = span.baggage().get(KEY);
+            assertThat("Retrieval of unset baggage", actual, OptionalMatcher.optionalEmpty());
+        });
+    }
+
+    @Test void baggageRetrievalWhenNeverTouchedShouldBeEmptyFromContext() {
+        runWithSpanHarness(span -> {
+            // NPE in 4.0.5 -> 4.0.10
+            final var actual = span.context().baggage().get(KEY);
+            assertThat("Retrieval of unset baggage", actual, OptionalMatcher.optionalEmpty());
+        });
+    }
+
+    @Test void baggageRetrievalWhenNeverTouchedShouldBeEmptyFromSpanWithWorkaround() {
+        runWithSpanHarness(span -> {
+            // Apply only known workaround (use containsKey precondition)
+            final var actual = !span.baggage().containsKey(KEY) ? Optional.empty()
+                    : span.baggage().get(KEY);
+            assertThat("Retrieval of unset baggage", actual, OptionalMatcher.optionalEmpty());
+        });
+    }
+
+    private void runWithSpanHarness(final Consumer<Span> spanConsumer) {
+        final var span = Tracer.global().spanBuilder("BaggageOptionalTest").start();
+        try {
+            spanConsumer.accept(span);
+
+        } finally {
+            span.end();
+        }
+    }
+}