Allow for setting connector provider via properties (#5345)

* Allow for setting connector provider via properties

Signed-off-by: jansupol <jan.supol@oracle.com>
diff --git a/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/MetaInfOverrideTest.java b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/MetaInfOverrideTest.java
new file mode 100644
index 0000000..2b60e14
--- /dev/null
+++ b/connectors/helidon-connector/src/test/java/org/glassfish/jersey/helidon/connector/MetaInfOverrideTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.helidon.connector;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.client.HttpUrlConnectorProvider;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+
+// The Helidon jar has META-INF set
+// Test of override
+class MetaInfOverrideTest extends JerseyTest {
+
+    @Path("/origin")
+    public static class UserAgentServer {
+        @GET
+        public String get(@Context HttpHeaders headers) {
+            return headers.getHeaderString(HttpHeaders.USER_AGENT);
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        return new ResourceConfig(UserAgentServer.class);
+    }
+
+    @Test
+    void defaultMetaInfTest() {
+        try (Response r = target("origin").request().get()) {
+            Assertions.assertEquals(200, r.getStatus());
+            Assertions.assertTrue(r.readEntity(String.class).contains("Helidon"));
+        }
+    }
+
+    @Test
+    void overrideMetaInfTest() {
+        ClientConfig config = new ClientConfig();
+        config.connectorProvider(new HttpUrlConnectorProvider());
+        try (Response r = ClientBuilder.newClient(config).target(target("origin").getUri()).request().get()) {
+            Assertions.assertEquals(200, r.getStatus());
+            r.bufferEntity();
+            System.out.println(r.readEntity(String.class));
+            Assertions.assertTrue(r.readEntity(String.class).contains("HttpUrlConnection"));
+        }
+    }
+
+    @Test
+    void overrideMetaInfByOtherConfigPropertyTest() {
+        ClientConfig config = new ClientConfig();
+        config.property(ClientProperties.CONNECTOR_PROVIDER, "org.glassfish.jersey.client.HttpUrlConnectorProvider");
+        try (Response r = ClientBuilder.newClient(config).target(target("origin").getUri()).request().get()) {
+            Assertions.assertEquals(200, r.getStatus());
+            r.bufferEntity();
+            System.out.println(r.readEntity(String.class));
+            Assertions.assertTrue(r.readEntity(String.class).contains("HttpUrlConnection"));
+        }
+    }
+
+    @Test
+    void overrideMetaInfByThePropertyTest() {
+        try (Response r = ClientBuilder.newBuilder()
+                .property(ClientProperties.CONNECTOR_PROVIDER, "org.glassfish.jersey.client.HttpUrlConnectorProvider")
+                .build()
+                .target(target("origin").getUri()).request().get()) {
+            Assertions.assertEquals(200, r.getStatus());
+            r.bufferEntity();
+            System.out.println(r.readEntity(String.class));
+            Assertions.assertTrue(r.readEntity(String.class).contains("HttpUrlConnection"));
+        }
+    }
+}
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java b/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java
index e5600ce..a1a6371 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/ClientProperties.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -24,6 +24,8 @@
 import org.glassfish.jersey.internal.util.PropertiesHelper;
 import org.glassfish.jersey.internal.util.PropertyAlias;
 
+import javax.ws.rs.client.ClientBuilder;
+
 /**
  * Jersey client implementation configuration properties.
  *
@@ -444,7 +446,7 @@
             EXPECT_100_CONTINUE_THRESHOLD_SIZE = "jersey.config.client.request.expect.100.continue.threshold.size";
 
     /**
-     * Default threshold size (64kb) after which which Expect:100-Continue header would be applied before
+     * Default threshold size (64kb) after which Expect:100-Continue header would be applied before
      * the main request.
      *
      * @since 2.32
@@ -463,6 +465,22 @@
      */
     public static final String QUERY_PARAM_STYLE = "jersey.config.client.uri.query.param.style";
 
+    /**
+     * Sets the {@link org.glassfish.jersey.client.spi.ConnectorProvider} class. Overrides the value from META-INF/services.
+     *
+     * <p>
+     *     The value MUST be an instance of {@code String}.
+     * </p>
+     * <p>
+     *     The property is recognized by {@link ClientBuilder}.
+     * </p>
+     * <p>
+     *     The name of the configuration property is <tt>{@value}</tt>.
+     * </p>
+     * @since 2.40
+     */
+    public static final String CONNECTOR_PROVIDER = "jersey.config.client.connector.provider";
+
     private ClientProperties() {
         // prevents instantiation
     }
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/JerseyClientBuilder.java b/core-client/src/main/java/org/glassfish/jersey/client/JerseyClientBuilder.java
index 8ad0eea..03d4860 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/JerseyClientBuilder.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/JerseyClientBuilder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -16,6 +16,7 @@
 
 package org.glassfish.jersey.client;
 
+import java.security.AccessController;
 import java.security.KeyStore;
 import java.util.Collections;
 import java.util.LinkedList;
@@ -32,9 +33,13 @@
 import javax.net.ssl.SSLContext;
 
 import org.glassfish.jersey.SslConfigurator;
+import org.glassfish.jersey.client.innate.inject.NonInjectionManager;
 import org.glassfish.jersey.client.internal.LocalizationMessages;
 import org.glassfish.jersey.client.spi.ClientBuilderListener;
+import org.glassfish.jersey.client.spi.ConnectorProvider;
 import org.glassfish.jersey.internal.ServiceFinder;
+import org.glassfish.jersey.internal.config.ExternalPropertiesConfigurationFactory;
+import org.glassfish.jersey.internal.util.ReflectionHelper;
 import org.glassfish.jersey.internal.util.collection.UnsafeValue;
 import org.glassfish.jersey.internal.util.collection.Values;
 import org.glassfish.jersey.model.internal.RankedComparator;
@@ -186,6 +191,9 @@
 
     @Override
     public JerseyClient build() {
+        ExternalPropertiesConfigurationFactory.configure(this.config);
+        setConnectorFromProperties();
+
         if (sslContext != null) {
             return new JerseyClient(config, sslContext, hostnameVerifier, null);
         } else if (sslConfigurator != null) {
@@ -204,6 +212,20 @@
         }
     }
 
+    private void setConnectorFromProperties() {
+        final Object connectorClass = config.getProperty(ClientProperties.CONNECTOR_PROVIDER);
+        if (connectorClass != null) {
+            if (String.class.isInstance(connectorClass)) {
+                Class<? extends ConnectorProvider> clazz
+                        = AccessController.doPrivileged(ReflectionHelper.classForNamePA((String) connectorClass));
+                final ConnectorProvider connectorProvider = new NonInjectionManager().justCreate(clazz);
+                config.connectorProvider(connectorProvider);
+            } else {
+                throw new IllegalArgumentException();
+            }
+        }
+    }
+
     @Override
     public ClientConfig getConfiguration() {
         return config;
diff --git a/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionManager.java b/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionManager.java
index 74ccd8c..8bd5112 100644
--- a/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionManager.java
+++ b/core-client/src/main/java/org/glassfish/jersey/client/innate/inject/NonInjectionManager.java
@@ -418,10 +418,31 @@
         }
 
         ClassBindings<T> classBindings = classBindings(createMe);
-        return classBindings.create();
+        return classBindings.create(true);
     }
 
-    private <T> T justCreate(Class<T> createMe) {
+    @Override
+    public <T> T createAndInitialize(Class<T> createMe) {
+        checkShutdown();
+
+        if (InjectionManager.class.equals(createMe)) {
+            return (T) this;
+        }
+        if (RequestScope.class.equals(createMe)) {
+            if (!isRequestScope) {
+                isRequestScope = true;
+                return (T) new NonInjectionRequestScope();
+            } else {
+                throw new IllegalStateException(LocalizationMessages.NONINJECT_REQUESTSCOPE_CREATED());
+            }
+        }
+
+        ClassBindings<T> classBindings = classBindings(createMe);
+        T t = classBindings.create(false);
+        return t != null ? t : justCreate(createMe);
+    }
+
+    public <T> T justCreate(Class<T> createMe) {
         T result = null;
         try {
             Constructor<T> mostArgConstructor = findConstructor(createMe);
@@ -493,11 +514,6 @@
     }
 
     @Override
-    public <T> T createAndInitialize(Class<T> createMe) {
-        return justCreate(createMe);
-    }
-
-    @Override
     public void inject(Object injectMe) {
         Method postConstruct = getAnnotatedMethod(injectMe, PostConstruct.class);
         if (postConstruct != null) {
@@ -750,7 +766,7 @@
             return t;
         }
 
-        X create() {
+        X create(boolean throwWhenNoBinding) {
             _checkUnique();
             if (!instanceBindings.isEmpty()) {
                 return _getInstance(instanceBindings.get(0));
@@ -762,7 +778,11 @@
                 return _create(supplierClassBindings.get(0));
             }
 
-            throw new IllegalStateException(LocalizationMessages.NONINJECT_NO_BINDING(type));
+            if (throwWhenNoBinding) {
+                throw new IllegalStateException(LocalizationMessages.NONINJECT_NO_BINDING(type));
+            } else {
+                return null;
+            }
         }
 
         protected X getInstance() {
@@ -770,7 +790,7 @@
             if (instance != null) {
                 return instance;
             }
-            return create();
+            return create(true);
         }
 
         List<X> allInstances() {
@@ -931,7 +951,7 @@
 
         @SuppressWarnings("unchecked")
         @Override
-        T create() {
+        T create(boolean throwWhenNoBinding) {
             if (ParameterizedType.class.isInstance(type)) {
                 ParameterizedType pt = (ParameterizedType) type;
                 if (Provider.class.equals(pt.getRawType())) {
@@ -955,7 +975,7 @@
                     };
                 }
             }
-            return super.create();
+            return super.create(throwWhenNoBinding);
         }
     }
 
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesAutoDiscoverable.java b/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesAutoDiscoverable.java
deleted file mode 100644
index e7d0adc..0000000
--- a/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesAutoDiscoverable.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v. 2.0, which is available at
- * http://www.eclipse.org/legal/epl-2.0.
- *
- * This Source Code may also be made available under the following Secondary
- * Licenses when the conditions for such availability set forth in the
- * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
- * version 2 with the GNU Classpath Exception, which is available at
- * https://www.gnu.org/software/classpath/license.html.
- *
- * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
- */
-
-package org.glassfish.jersey.internal.config;
-
-import org.glassfish.jersey.internal.spi.AutoDiscoverable;
-
-import javax.annotation.Priority;
-import javax.ws.rs.ConstrainedTo;
-import javax.ws.rs.RuntimeType;
-import javax.ws.rs.core.FeatureContext;
-
-@ConstrainedTo(RuntimeType.CLIENT) //server is configured directly in ResourceConfig
-@Priority(AutoDiscoverable.DEFAULT_PRIORITY)
-public class ExternalPropertiesAutoDiscoverable implements AutoDiscoverable {
-    @Override
-    public void configure(FeatureContext context) {
-        if (!context.getConfiguration().isRegistered(ExternalPropertiesConfigurationFeature.class)) {
-            context.register(ExternalPropertiesConfigurationFeature.class);
-        }
-    }
-}
\ No newline at end of file
diff --git a/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFeature.java b/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFeature.java
deleted file mode 100644
index acdab23..0000000
--- a/core-common/src/main/java/org/glassfish/jersey/internal/config/ExternalPropertiesConfigurationFeature.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (c) 2019 Oracle and/or its affiliates. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v. 2.0, which is available at
- * http://www.eclipse.org/legal/epl-2.0.
- *
- * This Source Code may also be made available under the following Secondary
- * Licenses when the conditions for such availability set forth in the
- * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
- * version 2 with the GNU Classpath Exception, which is available at
- * https://www.gnu.org/software/classpath/license.html.
- *
- * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
- */
-
-package org.glassfish.jersey.internal.config;
-
-import javax.ws.rs.core.Feature;
-import javax.ws.rs.core.FeatureContext;
-
-public class ExternalPropertiesConfigurationFeature implements Feature {
-
-    @Override
-    public boolean configure(FeatureContext configurableContext) {
-        return ExternalPropertiesConfigurationFactory.configure(configurableContext);
-    }
-
-}
\ No newline at end of file
diff --git a/core-common/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable b/core-common/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable
index abc1825..371b37b 100644
--- a/core-common/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable
+++ b/core-common/src/main/resources/META-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable
@@ -1,2 +1 @@
-org.glassfish.jersey.logging.LoggingFeatureAutoDiscoverable
-org.glassfish.jersey.internal.config.ExternalPropertiesAutoDiscoverable
\ No newline at end of file
+org.glassfish.jersey.logging.LoggingFeatureAutoDiscoverable
\ No newline at end of file
diff --git a/docs/src/main/docbook/appendix-properties.xml b/docs/src/main/docbook/appendix-properties.xml
index 56fd939..887b689 100644
--- a/docs/src/main/docbook/appendix-properties.xml
+++ b/docs/src/main/docbook/appendix-properties.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0"?>
 <!--
 
-    Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved.
+    Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved.
 
     This program and the accompanying materials are made available under the
     terms of the Eclipse Public License v. 2.0, which is available at
@@ -882,6 +882,21 @@
                         </entry>
                     </row>
                     <row>
+                        <entry>&jersey.client.ClientProperties.CONNECTOR_PROVIDER;(Jersey 2.40 or later)</entry>
+                        <entry><literal>jersey.config.client.connector.provider</literal></entry>
+                        <entry>
+                            <para>
+                                Sets the &jersey.client.ConnectorProvider; class. Overrides the value from META-INF/services.
+                            </para>
+                            <para>
+                                The value MUST be an instance of &lit.jdk6.String;.
+                            </para>
+                            <para>
+                                The property is recognized by &lit.jaxrs.client.ClientBuilder;.
+                            </para>
+                        </entry>
+                    </row>
+                    <row>
                         <entry>&jersey.client.ClientProperties.FEATURE_AUTO_DISCOVERY_DISABLE;</entry>
                         <entry><literal>jersey.config.client.disableAutoDiscovery</literal></entry>
                         <entry>
diff --git a/docs/src/main/docbook/client.xml b/docs/src/main/docbook/client.xml
index d9719b6..85e9fd8 100644
--- a/docs/src/main/docbook/client.xml
+++ b/docs/src/main/docbook/client.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0"?>
 <!--
 
-    Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved.
+    Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved.
 
     This program and the accompanying materials are made available under the
     terms of the Eclipse Public License v. 2.0, which is available at
@@ -761,6 +761,16 @@
             register &lit.jersey.client.Connector; instances in the Jersey &jersey.client.ClientConfig;. The new
             &lit.jersey.client.ConnectorProvider; SPI must be used instead to configure a custom client-side transport connector.
         </para>
+        <para>
+            A &jersey.client.ConnectorProvider; can also be set by a property on a &lit.jaxrs.client.ClientBuilder; starting with
+            Jersey 2.40. The following example shows how to setup the custom Grizzly Asynchronous HTTP Client based
+            &lit.jersey.client.ConnectorProvider; in a Jersey client instance:
+            <programlisting language="java" linenumbering="numbered">Client client = ClientBuilder.newBuilder()
+    .property(ClientProperties.CONNECTOR_PROVIDER, "org.glassfish.jersey.grizzly.connector.GrizzlyConnectorProvider")
+    .build();</programlisting>
+
+            For more information about the property see <xref linkend="appendix-properties"/>.
+        </para>
         <section>
             <title>Client Connectors Properties</title>
             <para>
diff --git a/docs/src/main/docbook/jersey.ent b/docs/src/main/docbook/jersey.ent
index a729162..733df58 100644
--- a/docs/src/main/docbook/jersey.ent
+++ b/docs/src/main/docbook/jersey.ent
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="iso-8859-1" ?>
 <!--
 
-    Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved.
+    Copyright (c) 2010, 2023 Oracle and/or its affiliates. All rights reserved.
 
     This program and the accompanying materials are made available under the
     terms of the Eclipse Public License v. 2.0, which is available at
@@ -336,6 +336,7 @@
 <!ENTITY jersey.client.ClientProperties.BUFFER_RESPONSE_ENTITY_ON_EXCEPTION "<link xlink:href='&jersey.javadoc.uri.prefix;/client/ClientProperties.html#BUFFER_RESPONSE_ENTITY_ON_EXCEPTION'>ClientProperties.BUFFER_RESPONSE_ENTITY_ON_EXCEPTION</link>" >
 <!ENTITY jersey.client.ClientProperties.CHUNKED_ENCODING_SIZE "<link xlink:href='&jersey.javadoc.uri.prefix;/client/ClientProperties.html#CHUNKED_ENCODING_SIZE'>ClientProperties.CHUNKED_ENCODING_SIZE</link>" >
 <!ENTITY jersey.client.ClientProperties.CONNECT_TIMEOUT "<link xlink:href='&jersey.javadoc.uri.prefix;/client/ClientProperties.html#CONNECT_TIMEOUT'>ClientProperties.CONNECT_TIMEOUT</link>" >
+<!ENTITY jersey.client.ClientProperties.CONNECTOR_PROVIDER "<link xlink:href='&jersey.javadoc.uri.prefix;/client/ClientProperties.html#CONNECTOR_PROVIDER'>ClientProperties.CONNECTOR_PROVIDER</link>" >
 <!ENTITY jersey.client.ClientProperties.DEFAULT_CHUNK_SIZE "<link xlink:href='&jersey.javadoc.uri.prefix;/client/ClientProperties.html#DEFAULT_CHUNK_SIZE'>ClientProperties.DEFAULT_CHUNK_SIZE</link>" >
 <!ENTITY jersey.client.ClientProperties.FEATURE_AUTO_DISCOVERY_DISABLE "<link xlink:href='&jersey.javadoc.uri.prefix;/client/ClientProperties.html#FEATURE_AUTO_DISCOVERY_DISABLE'>ClientProperties.FEATURE_AUTO_DISCOVERY_DISABLE</link>" >
 <!ENTITY jersey.client.ClientProperties.FOLLOW_REDIRECTS "<link xlink:href='&jersey.javadoc.uri.prefix;/client/ClientProperties.html#FOLLOW_REDIRECTS'>ClientProperties.FOLLOW_REDIRECTS</link>" >
diff --git a/examples/configured-client/README.MD b/examples/configured-client/README.MD
new file mode 100644
index 0000000..94a66be
--- /dev/null
+++ b/examples/configured-client/README.MD
@@ -0,0 +1,40 @@
+[//]: # " Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Client Configured by Property File Example
+==========================================
+
+This example demonstrates configuration of a Client using property file.
+The property file microprofile-config.properties is consumed by 
+Microprofile Config implementation and the properties from the 
+property file are set to Jersey Configuration.
+
+The following properties are defined in `microprofile-config.properties` property file:
+  * jersey.config.client.connector.provider - jersey property name for the connector to be used
+  * entity.value - user defined property to be be sent as an echo message  
+
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path             | Resource class      | HTTP methods | Notes
+-------------------- | ------------------- |--------------| --------------------------------------------------------
+**_/helloworld_**    | HelloWorldResource  | POST         |  Echoes the message sent
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+>     mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container.
+
+-   <http://localhost:8080/base/helloworld>
diff --git a/examples/configured-client/pom.xml b/examples/configured-client/pom.xml
new file mode 100644
index 0000000..6206197
--- /dev/null
+++ b/examples/configured-client/pom.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey.examples</groupId>
+        <artifactId>project</artifactId>
+        <version>2.40-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>configured-client</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-examples-configured-client</name>
+
+    <description>Jersey client configured by a property file example.</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.containers</groupId>
+            <artifactId>jersey-container-grizzly2-http</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.inject</groupId>
+            <artifactId>jersey-hk2</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.connectors</groupId>
+            <artifactId>jersey-apache5-connector</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.ext.microprofile</groupId>
+            <artifactId>jersey-mp-config</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.helidon.microprofile.config</groupId>
+            <artifactId>helidon-microprofile-config</artifactId>
+            <version>${helidon.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.eclipse.microprofile.config</groupId>
+                    <artifactId>microprofile-config-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+<!--    Instead of helidon-microprofile-config, smallrye-config can be used -->
+<!--        <dependency>-->
+<!--            <groupId>io.smallrye</groupId>-->
+<!--            <artifactId>smallrye-config</artifactId>-->
+<!--            <version>${smallrye.config.version}</version>-->
+<!--            <exclusions>-->
+<!--                <exclusion>-->
+<!--                    <groupId>org.eclipse.microprofile.config</groupId>-->
+<!--                    <artifactId>microprofile-config-api</artifactId>-->
+<!--                </exclusion>-->
+<!--            </exclusions>-->
+<!--        </dependency>-->
+
+        <dependency>
+            <groupId>org.glassfish.jersey.test-framework</groupId>
+            <artifactId>jersey-test-framework-util</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+            <artifactId>jersey-test-framework-provider-bundle</artifactId>
+            <type>pom</type>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>org.glassfish.jersey.examples.configured.client.App</mainClass>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>pre-release</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-assembly-plugin</artifactId>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+</project>
diff --git a/examples/configured-client/src/main/java/org/glassfish/jersey/examples/configured/client/App.java b/examples/configured-client/src/main/java/org/glassfish/jersey/examples/configured/client/App.java
new file mode 100644
index 0000000..7190358
--- /dev/null
+++ b/examples/configured-client/src/main/java/org/glassfish/jersey/examples/configured/client/App.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.configured.client;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.client.ClientProperties;
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+/**
+ * Hello world!
+ */
+public class App {
+
+    private static final URI BASE_URI = URI.create("http://localhost:8080/base/");
+    public static final String ROOT_PATH = "helloworld";
+    /* package */ static final String ENTITY_PROPERTY = "entity.value";
+
+    public static void main(String[] args) {
+        try {
+            System.out.println("\"Hello World\" Jersey Example App");
+
+            final ResourceConfig resourceConfig = new ResourceConfig(HelloWorldResource.class);
+            final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, resourceConfig, false);
+            Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    server.shutdownNow();
+                }
+            }));
+            server.start();
+
+            WebTarget target = ClientBuilder.newClient().target(BASE_URI);
+            Object entity = target.getConfiguration().getProperty(ENTITY_PROPERTY);
+            Object provider = target.getConfiguration().getProperty(ClientProperties.CONNECTOR_PROVIDER);
+
+            System.out
+                    .append("  Application started.\n")
+                    .append("  Sending entity \"").append((String) entity).append("\"")
+                    .append(" using ").append((String) provider).append(" connector provider")
+                    .append(" to echo resource ").append(BASE_URI.toASCIIString()).println(ROOT_PATH);
+
+            try (Response response = target.path(ROOT_PATH).request().post(Entity.entity(entity, MediaType.TEXT_PLAIN_TYPE))) {
+                System.out.append("  Recieved: \"").append(response.readEntity(String.class)).println("\"");
+            }
+
+            server.stop();
+            System.exit(0);
+        } catch (IOException ex) {
+            Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+        }
+
+    }
+}
diff --git a/examples/configured-client/src/main/java/org/glassfish/jersey/examples/configured/client/HelloWorldResource.java b/examples/configured-client/src/main/java/org/glassfish/jersey/examples/configured/client/HelloWorldResource.java
new file mode 100644
index 0000000..1c91d66
--- /dev/null
+++ b/examples/configured-client/src/main/java/org/glassfish/jersey/examples/configured/client/HelloWorldResource.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.configured.client;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+
+
+@Path("helloworld")
+public class HelloWorldResource {
+    @Context
+    Configuration configuration;
+
+    @POST
+    @Produces("text/plain")
+    public String postHello(String helloMsg) {
+        return helloMsg;
+    }
+
+    @GET
+    @Path("agent")
+    @Produces(MediaType.TEXT_PLAIN)
+    public String getAgent(@Context HttpHeaders headers) {
+        return headers.getHeaderString(HttpHeaders.USER_AGENT);
+    }
+
+}
diff --git a/examples/configured-client/src/main/resources/META-INF/microprofile-config.properties b/examples/configured-client/src/main/resources/META-INF/microprofile-config.properties
new file mode 100644
index 0000000..5c6cbb2
--- /dev/null
+++ b/examples/configured-client/src/main/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,12 @@
+#
+# Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Distribution License v. 1.0, which is available at
+# http://www.eclipse.org/org/documents/edl-v10.php.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+jersey.config.client.connector.provider=org.glassfish.jersey.apache5.connector.Apache5ConnectorProvider
+entity.value=Hello World!
\ No newline at end of file
diff --git a/examples/configured-client/src/test/java/org/glassfish/jersey/examples/configured/client/HelloWorldTest.java b/examples/configured-client/src/test/java/org/glassfish/jersey/examples/configured/client/HelloWorldTest.java
new file mode 100644
index 0000000..88f99c2
--- /dev/null
+++ b/examples/configured-client/src/test/java/org/glassfish/jersey/examples/configured/client/HelloWorldTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.configured.client;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.parallel.Execution;
+import org.junit.jupiter.api.parallel.ExecutionMode;
+import org.junit.jupiter.api.parallel.ResourceAccessMode;
+import org.junit.jupiter.api.parallel.ResourceLock;
+
+import static org.glassfish.jersey.examples.configured.client.App.ENTITY_PROPERTY;
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public class HelloWorldTest extends JerseyTest {
+
+    @Override
+    protected ResourceConfig configure() {
+        // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory
+        // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory
+        // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.jdkhttp.JdkHttpServerTestContainerFactory
+        // mvn test -Djersey.config.test.container.factory=org.glassfish.jersey.test.simple.SimpleTestContainerFactory
+        enable(TestProperties.LOG_TRAFFIC);
+        // enable(TestProperties.DUMP_ENTITY);
+        return new ResourceConfig(HelloWorldResource.class);
+    }
+
+    @Test
+    @Execution(ExecutionMode.CONCURRENT)
+    @ResourceLock(value = "dummy", mode = ResourceAccessMode.READ)
+    public void testEntity() {
+        WebTarget target = target("helloworld");
+        Object entity = target.getConfiguration().getProperty(ENTITY_PROPERTY);
+        try (Response response = target.request().post(Entity.entity(entity, MediaType.TEXT_PLAIN_TYPE))) {
+            Assertions.assertEquals(200, response.getStatus());
+            String readEntity = response.readEntity(String.class);
+            System.out.println(entity);
+            Assertions.assertEquals(entity, readEntity);
+        }
+    }
+
+    @Test
+    @Execution(ExecutionMode.CONCURRENT)
+    @ResourceLock(value = "dummy", mode = ResourceAccessMode.READ)
+    public void testConnector() {
+        try (Response response = target("helloworld").path("agent").request().get()) {
+            Assertions.assertEquals(200, response.getStatus());
+            String entity = response.readEntity(String.class);
+            System.out.println(entity);
+            Assertions.assertTrue(entity.contains("Apache HttpClient 5"));
+        }
+    }
+}
diff --git a/examples/pom.xml b/examples/pom.xml
index 138e57f..3192e79 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -60,6 +60,7 @@
         <module>cdi-webapp</module>
         <module>clipboard</module>
         <module>clipboard-programmatic</module>
+        <module>configured-client</module>
         <module>declarative-linking</module>
         <module>entity-filtering</module>
         <module>entity-filtering-selectable</module>