Repository: helidon-io/helidon (java)

Issue:
[4.x] `Span.current()` does not return `Optional.empty()` if there is no current span, contrary to the documented contract (OTel)
Backport of #8573 for Helidon 4.x

Pull request:
Properly return Optional.empty() for current span if there is no current OTel span
### Description
Resolves #8575 

Basically a forward-port of the 3.x fix for the same problem; see the comment there for details.  https://github.com/helidon-io/helidon/pull/8574#issue-2211654801

### Documentation
But fix; no doc impact

Patch:
diff --git a/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/OpenTelemetryTracerProvider.java b/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/OpenTelemetryTracerProvider.java
index 6d0396118b..67b4dfb1eb 100644
--- a/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/OpenTelemetryTracerProvider.java
+++ b/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/OpenTelemetryTracerProvider.java
@@ -64,6 +64,13 @@ public class OpenTelemetryTracerProvider implements TracerProvider {
         });
     }
 
+    /**
+     * Creates a new provider; reserved for service loading.
+     */
+    @Deprecated
+    public OpenTelemetryTracerProvider() {
+    }
+
     /**
      * Register global tracer.
      *
@@ -90,10 +97,17 @@ public class OpenTelemetryTracerProvider implements TracerProvider {
      * @return optional of the current span
      */
     public static Optional<Span> activeSpan() {
-        // OTel returns a no-op span if there is no current one.
-        io.opentelemetry.api.trace.Span otelSpan = io.opentelemetry.api.trace.Span.current();
+        io.opentelemetry.context.Context otelContext = io.opentelemetry.context.Context.current();
+
+        // OTel Span.current() returns a no-op span if there is no current one. Use fromContextOrNull instead to distinguish.
+        io.opentelemetry.api.trace.Span otelSpan =
+                io.opentelemetry.api.trace.Span.fromContextOrNull(otelContext);
+
+        if (otelSpan == null) {
+            return Optional.empty();
+        }
 
-        // OTel returns empty baggage if there is no current one.
+        // OTel Baggage.current() returns empty baggage if there is no current one. That's OK for baggage.
         io.opentelemetry.api.baggage.Baggage otelBaggage = io.opentelemetry.api.baggage.Baggage.current();
 
         // Create the span directly with the retrieved baggage. Ideally, it will be our writable baggage because we had put it


Test patch:
diff --git a/tracing/providers/opentelemetry/src/test/java/io/helidon/tracing/providers/opentelemetry/TestSpanAndBaggage.java b/tracing/providers/opentelemetry/src/test/java/io/helidon/tracing/providers/opentelemetry/TestSpanAndBaggage.java
index 71846b814b..a6d757f027 100644
--- a/tracing/providers/opentelemetry/src/test/java/io/helidon/tracing/providers/opentelemetry/TestSpanAndBaggage.java
+++ b/tracing/providers/opentelemetry/src/test/java/io/helidon/tracing/providers/opentelemetry/TestSpanAndBaggage.java
@@ -89,14 +89,11 @@ class TestSpanAndBaggage {
             outerSpan.end(e);
         }
 
-        // There was no active span before outerSpan was activated, so expect the "default" ad-hoc span ID of all zeroes.
+        // There was no active span before outerSpan was activated, so expect an empty current span.
         Optional<Span> currentSpanAfterTryResourcesBlock = Span.current();
         assertThat("Current span just after try-resources block",
                    currentSpanAfterTryResourcesBlock,
-                   OptionalMatcher.optionalPresent());
-        assertThat("Current span just after try-resources block",
-                   currentSpanAfterTryResourcesBlock.get().context().spanId(),
-                   containsString("00000000"));
+                   OptionalMatcher.optionalEmpty());
     }
 
     @Test