Repository: helidon-io/helidon (java)

Issue:
`TracerProviderHelper#currentSpan()` throws NPE in some situations
## Environment Details
* Helidon Version: 4.x
* Helidon SE or Helidon MP
* JDK version:
* OS:
* Docker version (if applicable):

----------

## Problem Description
In some limited cases, the `TracerProviderHelper#currentSpan` method throws a NPE because `TRACER_PROVIDER` is null when `currentSpan` is invoked.

In at least two situations, customer code uses a custom `TracerProvider`. The static initializer in Helidon's `TracerProviderHelper` class uses service loading to locate and load such implementations, select one, and store a reference to it in the `TRACER_PROVIDER` static field. 

During its own initialization, that custom `TracerProvider` uses logging, and there is a custom logger in place which attempts to get the current span (if any) to add to log messages. And the attempt to get the current span ultimately invokes `TracerProviderHelper.currentSpan()` which depends on `TRACER_PROVIDER` being non-null.

In this scenario, though, `TRACER_PROVIDER` is still null because the static initializer has not yet finished. That triggers the NPE.

Although one could argue that the custom `TracerProvider` should not, even indirectly, invoke code that relies on `TRACER_PROVIDER` being non-null, this use case (and ones like it) would be difficult to untangle. Helidon could be of help to users in these situations by anticipating and dealing with this edge case.

Pull request:
[4.x] Guard against NPE during early invocation of Span.current()
### Description
Resolves #8254 

See description for details and related 3.x PR #8256 for explanation.



Patch:
diff --git a/.github/workflows/create-backport-issues.yml b/.github/workflows/create-backport-issues.yml
index c9c103efa4..5548be0748 100644
--- a/.github/workflows/create-backport-issues.yml
+++ b/.github/workflows/create-backport-issues.yml
@@ -9,7 +9,25 @@ on:
       version:
         description: 'Helidon version this issue was reported for'
         required: true
+        type: choice
+        options:
+          - 2.x
+          - 3.x
+          - 4.x
         default: '2.x'
+      target-2:
+        type: boolean
+        description: 'Port to 2.x?'
+        default:  false
+      target-3:
+        type: boolean
+        description: 'Port to 3.x?'
+        default: true
+      target-4:
+        type: boolean
+        description: 'Port to 4.x?'
+        default: true
+
 env:
   GITHUB_API_KEY: ${{ secrets.GITHUB_TOKEN }}
 
@@ -20,4 +38,4 @@ jobs:
     steps:
       - name: Check out repository code
         uses: actions/checkout@v2
-      - run: etc/scripts/actions/create-backport-issues.sh $GITHUB_REPOSITORY ${{ github.event.inputs.issue }} ${{ github.event.inputs.version }}
+      - run: etc/scripts/actions/create-backport-issues.sh $GITHUB_REPOSITORY ${{ github.event.inputs.issue }} ${{ github.event.inputs.version }} ${{ github.event.inputs.target-2 }} ${{ github.event.inputs.target-3 }} ${{ github.event.inputs.target-4 }}
diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/CompositeArrayBufferData.java b/common/buffers/src/main/java/io/helidon/common/buffers/CompositeArrayBufferData.java
index c9d33a6ccc..cf9492a721 100644
--- a/common/buffers/src/main/java/io/helidon/common/buffers/CompositeArrayBufferData.java
+++ b/common/buffers/src/main/java/io/helidon/common/buffers/CompositeArrayBufferData.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 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.
@@ -21,7 +21,7 @@ import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 
-class CompositeArrayBufferData extends ReadOnlyBufferData {
+class CompositeArrayBufferData extends ReadOnlyBufferData implements CompositeBufferData {
     private final BufferData[] data;
 
     CompositeArrayBufferData(BufferData[] data) {
@@ -38,6 +38,13 @@ class CompositeArrayBufferData extends ReadOnlyBufferData {
 
     @Override
     public void writeTo(OutputStream out) {
+        if (data.length == 1) {
+            BufferData datum = data[0];
+            if (!(datum instanceof CompositeBufferData)) {
+                datum.writeTo(out);
+                return;
+            }
+        }
         copy().writeTo(out);
     }
 
@@ -260,4 +267,9 @@ class CompositeArrayBufferData extends ReadOnlyBufferData {
     public String toString() {
         return "comp-array: a=" + available();
     }
+
+    @Override
+    public CompositeBufferData add(BufferData bufferData) {
+        throw new UnsupportedOperationException("Add not supported for " + getClass().getSimpleName() + " buffer");
+    }
 }
diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/CompositeListBufferData.java b/common/buffers/src/main/java/io/helidon/common/buffers/CompositeListBufferData.java
index bf5fcdaefe..66a405b73e 100644
--- a/common/buffers/src/main/java/io/helidon/common/buffers/CompositeListBufferData.java
+++ b/common/buffers/src/main/java/io/helidon/common/buffers/CompositeListBufferData.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 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.
@@ -56,6 +56,13 @@ class CompositeListBufferData extends ReadOnlyBufferData implements CompositeBuf
 
     @Override
     public void writeTo(OutputStream out) {
+        if (data.size() == 1) {
+            BufferData datum = data.get(0);
+            if (!(datum instanceof CompositeBufferData)) {
+                datum.writeTo(out);
+                return;
+            }
+        }
         copy().writeTo(out);
     }
 
diff --git a/docs/src/main/asciidoc/se/webclient.adoc b/docs/src/main/asciidoc/se/webclient.adoc
index a0d31a9564..64cf30a97d 100644
--- a/docs/src/main/asciidoc/se/webclient.adoc
+++ b/docs/src/main/asciidoc/se/webclient.adoc
@@ -1,6 +1,6 @@
 ///////////////////////////////////////////////////////////////////////////////
 
-    Copyright (c) 2020, 2023 Oracle and/or its affiliates.
+    Copyright (c) 2020, 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.
@@ -136,7 +136,7 @@ Once the request setup is completed, the following methods can be used to send i
 * `HttpClientResponse outputStream(OutputStreamHandler outputStreamConsumer)`
 * `<T> ClientResponseTyped<T> outputStream(OutputStreamHandler outputStreamConsumer, Class<T> requestedType)`
 
-Each of the methods will provide a way to allow response to be retrieved in a particular response type. Refer to link:{webclient-javadoc-base-url}..api/io/helidon/webclient/api/ClientRequest.html[ClientRequest API] for more details about these methods.
+Each of the methods will provide a way to allow response to be retrieved in a particular response type. Refer to link:{webclient-javadoc-base-url}.api/io/helidon/webclient/api/ClientRequest.html[ClientRequest API] for more details about these methods.
 
 .Execute a simple GET request to endpoint and receive a String response:
 [source,java]
diff --git a/etc/scripts/actions/create-backport-issues.sh b/etc/scripts/actions/create-backport-issues.sh
index 5925ff8c1b..12e1ae9cf1 100755
--- a/etc/scripts/actions/create-backport-issues.sh
+++ b/etc/scripts/actions/create-backport-issues.sh
@@ -1,6 +1,6 @@
 #!/bin/bash -l
 #
-# Copyright (c) 2022 Oracle and/or its affiliates.
+# Copyright (c) 2022, 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.
@@ -38,8 +38,8 @@ readonly REPOSITORY_FULL_NAME="$1"
 readonly ISSUE_NUMBER="$2"
 readonly HELIDON_VERSION="$3"
 
-if [ -z "${REPOSITORY_FULL_NAME}" -o -z "${ISSUE_NUMBER}" -o -z "${HELIDON_VERSION}" ]; then
-  echo "usage: $0 <repository-name> <issue-number> <helidon-version>"
+if [ -z "${REPOSITORY_FULL_NAME}" -o -z "${ISSUE_NUMBER}" -o -z "${HELIDON_VERSION}" -o $# -le 3 ]; then
+  echo "usage: $0 <repository-name> <issue-number> <helidon-version> <helidon-versions-to-port-to...>"
   exit 1
 fi
 
@@ -112,11 +112,15 @@ fi
 issue_title=$(sed "s/\"/'/g" <<< "$issue_title")
 
 ############################################################
-# For each version that is not the issue's version, add backport
+# For each version the caller specified add a porting issue.
 ############################################################
-for version in ${VERSIONS[@]}; do
-  if [ "$version" != "$HELIDON_VERSION" ]; then
-    # Create issue for all other versions and add the same labels and assignee
+version_targets=()
+next_version_to_check=2
+for is_version_selected in "${@:4}"; do
+  version=${next_version_to_check}.x
+  next_version_to_check=$((next_version_to_check+1))
+  if [ "$version" != "$HELIDON_VERSION" -a "$is_version_selected" = "true" ]; then
+    # Create issue for other indicated versions and add the same labels and assignee
     new_issue_title="[$version] ${issue_title}"
     new_issue_text="Backport of #${ISSUE_NUMBER} for Helidon ${version}"
 
diff --git a/http/http/src/main/java/io/helidon/http/HeadersImpl.java b/http/http/src/main/java/io/helidon/http/HeadersImpl.java
index 5cfc73718b..f11c6de17a 100644
--- a/http/http/src/main/java/io/helidon/http/HeadersImpl.java
+++ b/http/http/src/main/java/io/helidon/http/HeadersImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2022, 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.
@@ -34,10 +34,11 @@ class HeadersImpl<T extends WritableHeaders<T>> implements WritableHeaders<T> {
      Optimization for most commonly used header names
      */
     private final Header[] knownHeaders = new Header[KNOWN_HEADER_SIZE];
-    // custom (unknown) headers are slower
-    private final Map<HeaderName, Header> customHeaders = new HashMap<>();
     private IntSet knownHeaderIndices = new IntSet(KNOWN_HEADER_SIZE);
 
+    // custom (unknown) headers are slower
+    private Map<HeaderName, Header> customHeaders = null;
+
     HeadersImpl() {
     }
 
@@ -85,7 +86,7 @@ class HeadersImpl<T extends WritableHeaders<T>> implements WritableHeaders<T> {
 
     @Override
     public int size() {
-        return customHeaders.size() + knownHeaderIndices.size();
+        return (customHeaders == null ? 0 : customHeaders.size()) + knownHeaderIndices.size();
     }
 
     @Override
@@ -167,7 +168,7 @@ class HeadersImpl<T extends WritableHeaders<T>> implements WritableHeaders<T> {
         }
         int index = name.index();
         if (index == -1) {
-            customHeaders.put(name, usedHeader);
+            customHeaders().put(name, usedHeader);
         } else {
             knownHeaders[index] = usedHeader;
             knownHeaderIndices.add(index);
@@ -179,7 +180,7 @@ class HeadersImpl<T extends WritableHeaders<T>> implements WritableHeaders<T> {
     public T clear() {
         Arrays.fill(knownHeaders, null);
         knownHeaderIndices = new IntSet(KNOWN_HEADER_SIZE);
-        customHeaders.clear();
+        customHeaders().clear();
         return (T) this;
     }
 
@@ -231,8 +232,9 @@ class HeadersImpl<T extends WritableHeaders<T>> implements WritableHeaders<T> {
             knownHeaderIndices.remove(index);
             return value;
         }
-        return customHeaders.remove(name);
+        return customHeaders().remove(name);
     }
+
     private Header find(HeaderName name) {
         int index = name.index();
 
@@ -240,11 +242,18 @@ class HeadersImpl<T extends WritableHeaders<T>> implements WritableHeaders<T> {
             return knownHeaders[index];
         }
 
-        return customHeaders.get(name);
+        return customHeaders().get(name);
+    }
+
+    private Map<HeaderName, Header> customHeaders() {
+        if (customHeaders == null) {
+            customHeaders = new HashMap<>();
+        }
+        return customHeaders;
     }
 
     private class HeaderIterator implements Iterator<Header> {
-        private final boolean noCustom = customHeaders.isEmpty();
+        private final boolean noCustom = (customHeaders == null || customHeaders.isEmpty());
 
         private boolean inKnown = true;
         private int last = -1;
diff --git a/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java b/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java
index 399c178de4..c021515eaf 100644
--- a/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java
+++ b/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 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.
@@ -154,7 +154,7 @@ public class JwtAuthProvider implements AuthenticationProvider, OutboundSecurity
     private JwtAuthProvider(Builder builder) {
         this.optional = builder.optional;
         this.authenticate = builder.authenticate;
-        this.propagate = builder.propagate;
+        this.propagate = builder.propagate && builder.outboundConfig.targets().size() > 0;
         this.allowImpersonation = builder.allowImpersonation;
         this.subjectType = builder.subjectType;
         this.atnTokenHandler = builder.atnTokenHandler;
@@ -368,7 +368,8 @@ public class JwtAuthProvider implements AuthenticationProvider, OutboundSecurity
     public boolean isOutboundSupported(ProviderRequest providerRequest,
                                        SecurityEnvironment outboundEnv,
                                        EndpointConfig outboundConfig) {
-        return propagate;
+        // only propagate if we have an actual target configured
+        return propagate && this.outboundConfig.findTarget(outboundEnv).isPresent();
     }
 
     @Override
@@ -622,7 +623,7 @@ public class JwtAuthProvider implements AuthenticationProvider, OutboundSecurity
                 .tokenHeader("Authorization")
                 .tokenPrefix("bearer ")
                 .build();
-        private OutboundConfig outboundConfig;
+        private OutboundConfig outboundConfig = OutboundConfig.builder().build();
         private LazyValue<JwkKeys> verifyKeys;
         private LazyValue<JwkKeys> decryptionKeys;
         private LazyValue<Jwk> defaultJwk;
diff --git a/security/jwt/src/main/java/io/helidon/security/jwt/jwk/JwkKeys.java b/security/jwt/src/main/java/io/helidon/security/jwt/jwk/JwkKeys.java
index e891c62d0c..9e339384ce 100644
--- a/security/jwt/src/main/java/io/helidon/security/jwt/jwk/JwkKeys.java
+++ b/security/jwt/src/main/java/io/helidon/security/jwt/jwk/JwkKeys.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 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.
@@ -19,6 +19,9 @@ package io.helidon.security.jwt.jwk;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.System.Logger.Level;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -27,6 +30,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 
+import io.helidon.common.NativeImageHelper;
 import io.helidon.common.configurable.Resource;
 
 import jakarta.json.Json;
@@ -55,6 +59,26 @@ public final class JwkKeys {
     private final Map<String, Jwk> keyMap = new HashMap<>();
     private final List<Jwk> noKeyIdKeys = new LinkedList<>();
 
+    private static final boolean AUTOMATIC_CHARSET_DETECTION;
+
+    // Workaround for https://github.com/eclipse-ee4j/parsson/issues/121
+    static {
+        boolean utf32Available = false;
+        try {
+            Charset.forName("UTF-32LE");
+            Charset.forName("UTF-32BE");
+            utf32Available = true;
+        } catch (UnsupportedCharsetException e) {
+            if (NativeImageHelper.isNativeImage()) {
+                LOGGER.log(Level.TRACE, "Automatic JSON unicode detection not available."
+                        + " Add -H:+AddAllCharsets to build your native image with UTF-32 support.", e);
+            } else {
+                LOGGER.log(Level.TRACE, "Automatic JSON unicode detection not available.", e);
+            }
+        }
+        AUTOMATIC_CHARSET_DETECTION = utf32Available;
+    }
+
     private JwkKeys(Builder builder) {
         this.keyMap.putAll(builder.keyMap);
         this.noKeyIdKeys.addAll(builder.noKeyIdKeys);
@@ -147,7 +171,9 @@ public final class JwkKeys {
         public Builder resource(Resource resource) {
             Objects.requireNonNull(resource, "Json resource must not be null");
             try (InputStream is = resource.stream()) {
-                JsonObject jsonObject = JSON.createReader(is).readObject();
+                JsonObject jsonObject = AUTOMATIC_CHARSET_DETECTION
+                        ? JSON.createReader(is).readObject()
+                        : JSON.createReader(is, StandardCharsets.UTF_8).readObject();
                 addKeys(jsonObject);
             } catch (IOException e) {
                 LOGGER.log(Level.WARNING, "Failed to close input stream on resource: " + resource);
diff --git a/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/OpenTelemetryScope.java b/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/OpenTelemetryScope.java
index 89c5e4d620..0a96784bc9 100644
--- a/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/OpenTelemetryScope.java
+++ b/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/OpenTelemetryScope.java
@@ -21,21 +21,16 @@ import io.helidon.tracing.Scope;
 
 class OpenTelemetryScope implements Scope {
     private final io.opentelemetry.context.Scope delegate;
-    private final io.opentelemetry.context.Scope baggageScope;
     private final AtomicBoolean closed = new AtomicBoolean();
 
-    OpenTelemetryScope(io.opentelemetry.context.Scope scope, io.opentelemetry.context.Scope baggageScope) {
+    OpenTelemetryScope(io.opentelemetry.context.Scope scope) {
         delegate = scope;
-        this.baggageScope = baggageScope;
     }
 
     @Override
     public void close() {
         if (closed.compareAndSet(false, true) && delegate != null) {
             delegate.close();
-            if (baggageScope != null) {
-                baggageScope.close();
-            }
         }
     }
 
diff --git a/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/OpenTelemetrySpan.java b/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/OpenTelemetrySpan.java
index 3cae2355b1..519accb7f5 100644
--- a/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/OpenTelemetrySpan.java
+++ b/tracing/providers/opentelemetry/src/main/java/io/helidon/tracing/providers/opentelemetry/OpenTelemetrySpan.java
@@ -71,7 +71,7 @@ class OpenTelemetrySpan implements Span {
 
     @Override
     public SpanContext context() {
-        return new OpenTelemetrySpanContext(Context.current().with(delegate));
+        return new OpenTelemetrySpanContext(otelContextWithSpanAndBaggage());
     }
 
     @Override
@@ -93,8 +93,8 @@ class OpenTelemetrySpan implements Span {
 
     @Override
     public Scope activate() {
-        io.opentelemetry.context.Scope baggageScope = baggage.makeCurrent();
-        return new OpenTelemetryScope(delegate.makeCurrent(), baggageScope);
+        io.opentelemetry.context.Scope scope = otelContextWithSpanAndBaggage().makeCurrent();
+        return new OpenTelemetryScope(scope);
     }
 
     @Override
@@ -119,6 +119,12 @@ class OpenTelemetrySpan implements Span {
                 .orElseGet(Context::current);
     }
 
+    private Context otelContextWithSpanAndBaggage() {
+        // Because the Helidon tracing API links baggage with a span, any OTel context we create for the span
+        // needs to have the baggage with it.
+        return getContext().with(delegate).with(baggage);
+    }
+
     private Attributes toAttributes(Map<String, ?> attributes) {
         AttributesBuilder builder = Attributes.builder();
         attributes.forEach((key, value) -> {
diff --git a/tracing/tracing/src/main/java/io/helidon/tracing/TracerProviderHelper.java b/tracing/tracing/src/main/java/io/helidon/tracing/TracerProviderHelper.java
index a2907af39d..d5349accb5 100644
--- a/tracing/tracing/src/main/java/io/helidon/tracing/TracerProviderHelper.java
+++ b/tracing/tracing/src/main/java/io/helidon/tracing/TracerProviderHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019, 2022 Oracle and/or its affiliates.
+ * Copyright (c) 2019, 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.
@@ -58,18 +58,32 @@ final class TracerProviderHelper {
     }
 
     public static Optional<Span> currentSpan() {
-        return TRACER_PROVIDER.currentSpan();
+        /*
+        If a custom TracerProvider implementation indirectly tries to access the current span (for example, by triggering custom
+        logging that adds the current span to each message), then this method can be invoked before the static initializer has
+        completed and, therefore, before TRACER_PROVIDER is assigned.
+         */
+        return (TRACER_PROVIDER == null) ? Optional.empty() : TRACER_PROVIDER.currentSpan();
     }
 
     static Tracer global() {
+        if (TRACER_PROVIDER == null) {
+            throw new IllegalStateException("Use before initialization has completed");
+        }
         return TRACER_PROVIDER.global();
     }
 
     static void global(Tracer tracer) {
+        if (TRACER_PROVIDER == null) {
+            throw new IllegalStateException("Use before initialization has completed");
+        }
         TRACER_PROVIDER.global(tracer);
     }
 
     static TracerBuilder<?> findTracerBuilder() {
+        if (TRACER_PROVIDER == null) {
+            throw new IllegalStateException("Use before initialization has completed");
+        }
         return TRACER_PROVIDER.createBuilder();
     }
 }


Test patch:
diff --git a/microprofile/jwt-auth/src/test/java/io/helidon/microprofile/jwt/auth/JwtAuthProviderTest.java b/microprofile/jwt-auth/src/test/java/io/helidon/microprofile/jwt/auth/JwtAuthProviderTest.java
index 03b7841b01..334f5db18a 100644
--- a/microprofile/jwt-auth/src/test/java/io/helidon/microprofile/jwt/auth/JwtAuthProviderTest.java
+++ b/microprofile/jwt-auth/src/test/java/io/helidon/microprofile/jwt/auth/JwtAuthProviderTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2018, 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.
@@ -228,6 +228,53 @@ public class JwtAuthProviderTest {
                 }, () -> fail("User must be present in response"));
     }
 
+    @Test
+    public void testOutboundNotSupportedWithoutConfiguration() {
+        String username = "user1";
+        String userId = "user1-id";
+        String email = "user1@example.org";
+        String familyName = "Novak";
+        String givenName = "Standa";
+        String fullName = "Standa Novak";
+        Locale locale = Locale.CANADA_FRENCH;
+
+        Principal principal = Principal.builder()
+                .name(username)
+                .id(userId)
+                .addAttribute("email", email)
+                .addAttribute("email_verified", true)
+                .addAttribute("family_name", familyName)
+                .addAttribute("given_name", givenName)
+                .addAttribute("full_name", fullName)
+                .addAttribute("locale", locale)
+                .addAttribute("roles", Set.of("role1", "role2"))
+                .build();
+
+        Subject subject = Subject.builder()
+                .principal(principal)
+                .addGrant(Role.create("group1"))
+                .addGrant(Role.create("group2"))
+                .addGrant(Role.create("group3"))
+                .build();
+
+        JwtAuthProvider provider = JwtAuthProvider.create(Config.create().get("jwt-no-outbound"));
+
+        SecurityContext context = Mockito.mock(SecurityContext.class);
+        when(context.user()).thenReturn(Optional.of(subject));
+
+        ProviderRequest request = mock(ProviderRequest.class);
+        when(request.securityContext()).thenReturn(context);
+        SecurityEnvironment outboundEnv = SecurityEnvironment.builder()
+                .path("/ec")
+                .transport("http")
+                .targetUri(URI.create("http://localhost:8080/ec"))
+                .build();
+
+        EndpointConfig outboundEp = EndpointConfig.create();
+
+        assertThat(provider.isOutboundSupported(request, outboundEnv, outboundEp), is(false));
+    }
+
     @Test
     public void testOctBothWays() {
         String userId = "user1-id";
diff --git a/microprofile/jwt-auth/src/test/resources/application.yaml b/microprofile/jwt-auth/src/test/resources/application.yaml
index d4c30c51c9..775baa1b17 100644
--- a/microprofile/jwt-auth/src/test/resources/application.yaml
+++ b/microprofile/jwt-auth/src/test/resources/application.yaml
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2018, 2020 Oracle and/or its affiliates.
+# Copyright (c) 2018, 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.
@@ -70,4 +70,13 @@ security:
             - name: "asIs"
               paths:
                 - "/asis"
-    - abac:
\ No newline at end of file
+    - abac:
+
+
+jwt-no-outbound:
+  propagate: "true"
+  # Token extraction
+  atn-token:
+    # Expected audience (if not defined, any audience is accepted - security issue...)
+    jwt-audience: "audience.application.id"
+    default-key-id: "verify-rsa"
\ No newline at end of file
diff --git a/tracing/tracing/src/test/java/io/helidon/tracing/TestEarlyUseOfSpanCurrent.java b/tracing/tracing/src/test/java/io/helidon/tracing/TestEarlyUseOfSpanCurrent.java
new file mode 100644
index 0000000000..1150ade91d
--- /dev/null
+++ b/tracing/tracing/src/test/java/io/helidon/tracing/TestEarlyUseOfSpanCurrent.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.notNullValue;
+class TestEarlyUseOfSpanCurrent {
+
+    @Test
+    void ensureCurrentSpanNonNull() {
+
+        Span.current(); // Trigger the service loading of the test provider which will itself invoke Span.current().
+        assertThat("Early current span", TestTracerProvider.earlyCurrentSpan(), notNullValue());
+    }
+}
diff --git a/tracing/tracing/src/test/java/io/helidon/tracing/TestTracerProvider.java b/tracing/tracing/src/test/java/io/helidon/tracing/TestTracerProvider.java
new file mode 100644
index 0000000000..66abd44691
--- /dev/null
+++ b/tracing/tracing/src/test/java/io/helidon/tracing/TestTracerProvider.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import java.util.Optional;
+
+/**
+ * Approximates the behavior of a custom tracer provider with a constructor that invokes a logger which invokes Span.current().
+ */
+public class TestTracerProvider extends NoOpTracerProvider {
+
+    private static Optional<Span> earlyCurrentSpan;
+
+    public TestTracerProvider() {
+        Optional<Span> currentSpan;
+        try {
+            currentSpan = Span.current();
+        } catch (NullPointerException e) {
+            // silent
+            currentSpan = null;
+        }
+        earlyCurrentSpan = currentSpan;
+    }
+
+    static Optional<Span> earlyCurrentSpan() {
+        return earlyCurrentSpan;
+    }
+}
diff --git a/tracing/tracing/src/test/resources/META-INF/services/io.helidon.tracing.spi.TracerProvider b/tracing/tracing/src/test/resources/META-INF/services/io.helidon.tracing.spi.TracerProvider
new file mode 100644
index 0000000000..d11cd06103
--- /dev/null
+++ b/tracing/tracing/src/test/resources/META-INF/services/io.helidon.tracing.spi.TracerProvider
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+io.helidon.tracing.TestTracerProvider
\ No newline at end of file