Repository: helidon-io/helidon (java)

Issue:
`config-metadata.json` lacks configKey for ConfiguredProviders

## Environment Details
* Helidon Version: 4.0.8

----------

## Problem Description

[ConfiguredProvider](https://github.com/helidon-io/helidon/blob/4.0.8/common/config/src/main/java/io/helidon/common/config/ConfiguredProvider.java) defines a method `configKey()`. This key is not reflected anywhere in `config-metadata.json`

For example [Http1ProtocolConfigProvider](https://github.com/helidon-io/helidon/blob/4.0.8/webclient/http1/src/main/java/io/helidon/webclient/http1/Http1ProtocolConfigProvider.java) has a configKey of `http_1_1`. But the corresponding entry in config-metadata.json lacks that information:

```
  {
                "annotatedType": "io.helidon.webserver.http1.Http1Config",
                "type": "io.helidon.webserver.http1.Http1Config",
                "producers": [
                    "io.helidon.webserver.http1.Http1Config#create(io.helidon.common.config.Config)",
                    "io.helidon.webserver.http1.Http1Config#builder()"
                ],
                "provides": [
                    "io.helidon.webserver.spi.ProtocolConfig"
                ],
                "options": [
                 . . .
                ]
  }
```

## Background

My assumption is that `config-metadata.json` provides the schema for module configuration. It should be possible to derive a skeleton `application.yaml` using only the information provided in `config-metadata.json` files.  Currently that is not possible due to missing `configKey`

Pull request:
4.x: Fixed configuration metadata of blueprints that are configured and provide a service
Updated generated documentation to latest state of code.

Resolves #8776 

There were two problems in the code:
1. The protocol config blueprints used wrong provides (must match the `@Option.Provider(ProtocolConfigProvider.class)` in listener config)
2. The protocol config blueprints lacked the configured key, and setting that it is not a root configuration

After fixing the above problems, the generated documentation correctly shows the configuration keys, and the listener config documentation now lists all available implementations.

In addition the same problem was with observe providers. Fixed all providers to add config key, re-generated the documentation.

Added validation that `@Provides` and `@Configured` must have a configuration key.

Patch:
diff --git a/builder/codegen/src/main/java/io/helidon/builder/codegen/Types.java b/builder/codegen/src/main/java/io/helidon/builder/codegen/Types.java
index 8a274abb2c..3fb26ec743 100644
--- a/builder/codegen/src/main/java/io/helidon/builder/codegen/Types.java
+++ b/builder/codegen/src/main/java/io/helidon/builder/codegen/Types.java
@@ -43,6 +43,7 @@ final class Types {
     static final TypeName PROTOTYPE_ANNOTATED = TypeName.create("io.helidon.builder.api.Prototype.Annotated");
     static final TypeName PROTOTYPE_FACTORY = TypeName.create("io.helidon.builder.api.Prototype.Factory");
     static final TypeName PROTOTYPE_CONFIGURED = TypeName.create("io.helidon.builder.api.Prototype.Configured");
+    static final TypeName PROTOTYPE_PROVIDES = TypeName.create("io.helidon.builder.api.Prototype.Provides");
     static final TypeName PROTOTYPE_BUILDER = TypeName.create("io.helidon.builder.api.Prototype.Builder");
     static final TypeName PROTOTYPE_CONFIGURED_BUILDER = TypeName.create("io.helidon.builder.api.Prototype.ConfiguredBuilder");
     static final TypeName PROTOTYPE_CUSTOM_METHODS = TypeName.create("io.helidon.builder.api.Prototype.CustomMethods");
diff --git a/builder/codegen/src/main/java/io/helidon/builder/codegen/ValidationTask.java b/builder/codegen/src/main/java/io/helidon/builder/codegen/ValidationTask.java
index f0aa71335c..f9ff370e4a 100644
--- a/builder/codegen/src/main/java/io/helidon/builder/codegen/ValidationTask.java
+++ b/builder/codegen/src/main/java/io/helidon/builder/codegen/ValidationTask.java
@@ -23,6 +23,7 @@ import java.util.function.Consumer;
 import io.helidon.codegen.ElementInfoPredicates;
 import io.helidon.common.Errors;
 import io.helidon.common.types.AccessModifier;
+import io.helidon.common.types.Annotation;
 import io.helidon.common.types.TypeInfo;
 import io.helidon.common.types.TypeName;
 import io.helidon.common.types.TypedElementInfo;
@@ -41,7 +42,7 @@ abstract class ValidationTask {
         if (validatedType.interfaceTypeInfo()
                 .stream()
                 .noneMatch(it -> it.typeName().equals(implementedInterface))) {
-            errors.fatal(message);
+            errors.fatal(validatedType.typeName(), message);
         }
     }
 
@@ -70,7 +71,7 @@ abstract class ValidationTask {
                 })
                 .findFirst()
                 .isEmpty()) {
-            errors.fatal(validatedType.typeName().fqName(), message);
+            errors.fatal(validatedType.typeName(), message);
         }
     }
 
@@ -166,7 +167,21 @@ abstract class ValidationTask {
         public void validate(Errors.Collector errors) {
             // must be package local
             if (blueprint.accessModifier() == AccessModifier.PUBLIC) {
-                errors.fatal(blueprint.typeName().fqName() + " is defined as public, it must be package local");
+                errors.fatal(blueprint.typeName(), blueprint.typeName().fqName()
+                        + " is defined as public, it must be package local");
+            }
+
+            // if configured & provides, must have config key
+            if (blueprint.hasAnnotation(Types.PROTOTYPE_CONFIGURED)
+                && blueprint.hasAnnotation(Types.PROTOTYPE_PROVIDES)) {
+                Annotation configured = blueprint.annotation(Types.PROTOTYPE_CONFIGURED);
+                String value = configured.stringValue().orElse("");
+                if (value.isEmpty()) {
+                    // we have a @Configured and @Provides - this should have a configuration key!
+                    errors.fatal(blueprint.typeName(), blueprint.typeName().fqName()
+                            + " is marked as @Configured and @Provides, yet it does not"
+                                         + " define a configuration key");
+                }
             }
         }
 
@@ -276,8 +291,9 @@ abstract class ValidationTask {
             if (typeInfo.findAnnotation(annotation)
                     .stream()
                     .noneMatch(it -> it.value().map(expectedValue::equals).orElse(false))) {
-                errors.fatal("Type " + typeInfo.typeName()
-                        .fqName() + " must be annotated with " + annotation.fqName() + "(" + expectedValue + ")");
+                errors.fatal(typeInfo.typeName(),
+                             "Type " + typeInfo.typeName().fqName()
+                                     + " must be annotated with " + annotation.fqName() + "(" + expectedValue + ")");
             }
         }
     }
diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriQueryWriteable.java b/common/uri/src/main/java/io/helidon/common/uri/UriQueryWriteable.java
index c831889631..12310b8749 100644
--- a/common/uri/src/main/java/io/helidon/common/uri/UriQueryWriteable.java
+++ b/common/uri/src/main/java/io/helidon/common/uri/UriQueryWriteable.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.
@@ -85,8 +85,11 @@ public interface UriQueryWriteable extends UriQuery {
     UriQueryWriteable remove(String name, Consumer<List<String>> removedConsumer);
 
     /**
-     * Update from a query string (with decoded values).
-     * @param queryString decoded query string to update this instance
+     * Update from a query string (with <b>encoded</b> values).
+     * <p>
+     * This documentation (and behavior) has been changed, as we cannot create a proper query from {@code decoded} values,
+     *  as these may contain characters used to split the query.
+     * @param queryString encoded query string to update this instance
      */
     void fromQueryString(String queryString);
 
diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriQueryWriteableImpl.java b/common/uri/src/main/java/io/helidon/common/uri/UriQueryWriteableImpl.java
index 2dc04a5167..b6c2ba45aa 100644
--- a/common/uri/src/main/java/io/helidon/common/uri/UriQueryWriteableImpl.java
+++ b/common/uri/src/main/java/io/helidon/common/uri/UriQueryWriteableImpl.java
@@ -292,11 +292,21 @@ final class UriQueryWriteableImpl implements UriQueryWriteable {
     private void addRaw(String next) {
         int eq = next.indexOf('=');
         if (eq == -1) {
-            set(next);
+            addRaw(next, "");
         } else {
             String name = next.substring(0, eq);
             String value = next.substring(eq + 1);
-            set(name, value);
+            addRaw(name, value);
         }
     }
+
+    private void addRaw(String encodedName, String encodedValue) {
+        String decodedName = UriEncoding.decodeUri(encodedName);
+        String decodedValue = UriEncoding.decodeUri(encodedValue);
+
+        rawQueryParams.computeIfAbsent(encodedName, it -> new ArrayList<>(1))
+                .add(encodedValue);
+        decodedQueryParams.computeIfAbsent(decodedName, it -> new ArrayList<>(1))
+                .add(decodedValue);
+    }
 }
diff --git a/docs/src/main/asciidoc/config/io_helidon_webserver_ListenerConfig.adoc b/docs/src/main/asciidoc/config/io_helidon_webserver_ListenerConfig.adoc
index 5ab27735bf..a77b8f500b 100644
--- a/docs/src/main/asciidoc/config/io_helidon_webserver_ListenerConfig.adoc
+++ b/docs/src/main/asciidoc/config/io_helidon_webserver_ListenerConfig.adoc
@@ -110,7 +110,16 @@ Type: link:{javadoc-base-url}/io.helidon.webserver/io/helidon/webserver/Listener
  If configured to `0` (the default), server starts on a random port.
 
  Port to listen on (for the default socket)
-|`protocols` |io.helidon.webserver.spi.ProtocolConfig[&#93; (service provider interface) |{nbsp} |Configuration of protocols. This may be either protocol selectors, or protocol upgraders from HTTP/1.1.
+|`protocols` |io.helidon.webserver.spi.ProtocolConfig[&#93; (service provider interface)
+
+Such as:
+
+ - xref:{rootdir}/config/io_helidon_webserver_http2_Http2Config.adoc[http_2 (Http2Config)]
+ - xref:{rootdir}/config/io_helidon_webserver_grpc_GrpcConfig.adoc[grpc (GrpcConfig)]
+ - xref:{rootdir}/config/io_helidon_webserver_websocket_WsConfig.adoc[websocket (WsConfig)]
+ - xref:{rootdir}/config/io_helidon_webserver_http1_Http1Config.adoc[http_1_1 (Http1Config)]
+
+ |{nbsp} |Configuration of protocols. This may be either protocol selectors, or protocol upgraders from HTTP/1.1.
  As the order is not important (providers are ordered by weight by default), we can use a configuration as an object,
  such as:
  <pre>
diff --git a/docs/src/main/asciidoc/config/io_helidon_webserver_WebServer.adoc b/docs/src/main/asciidoc/config/io_helidon_webserver_WebServer.adoc
index cd8f542191..666d872763 100644
--- a/docs/src/main/asciidoc/config/io_helidon_webserver_WebServer.adoc
+++ b/docs/src/main/asciidoc/config/io_helidon_webserver_WebServer.adoc
@@ -126,7 +126,16 @@ Such as:
  If configured to `0` (the default), server starts on a random port.
 
  Port to listen on (for the default socket)
-|`protocols` |io.helidon.webserver.spi.ProtocolConfig[&#93; (service provider interface) |{nbsp} |Configuration of protocols. This may be either protocol selectors, or protocol upgraders from HTTP/1.1.
+|`protocols` |io.helidon.webserver.spi.ProtocolConfig[&#93; (service provider interface)
+
+Such as:
+
+ - xref:{rootdir}/config/io_helidon_webserver_http2_Http2Config.adoc[http_2 (Http2Config)]
+ - xref:{rootdir}/config/io_helidon_webserver_grpc_GrpcConfig.adoc[grpc (GrpcConfig)]
+ - xref:{rootdir}/config/io_helidon_webserver_websocket_WsConfig.adoc[websocket (WsConfig)]
+ - xref:{rootdir}/config/io_helidon_webserver_http1_Http1Config.adoc[http_1_1 (Http1Config)]
+
+ |{nbsp} |Configuration of protocols. This may be either protocol selectors, or protocol upgraders from HTTP/1.1.
  As the order is not important (providers are ordered by weight by default), we can use a configuration as an object,
  such as:
  <pre>
diff --git a/docs/src/main/asciidoc/config/io_helidon_webserver_cors_CorsConfig.adoc b/docs/src/main/asciidoc/config/io_helidon_webserver_cors_CorsConfig.adoc
index 9531104cc8..c57c843420 100644
--- a/docs/src/main/asciidoc/config/io_helidon_webserver_cors_CorsConfig.adoc
+++ b/docs/src/main/asciidoc/config/io_helidon_webserver_cors_CorsConfig.adoc
@@ -66,7 +66,7 @@ This type provides the following service implementations:
 |`sockets` |string[&#93; |{nbsp} |List of sockets to register this feature on. If empty, it would get registered on all sockets.
 
  Socket names to register on, defaults to empty (all available sockets)
-|`weight` |double |`950.0` |Weight of the CORS feature. As it is used by other features, the default is quite high:
+|`weight` |double |`850.0` |Weight of the CORS feature. As it is used by other features, the default is quite high:
  CorsFeature.WEIGHT.
 
  Weight of the feature
diff --git a/docs/src/main/asciidoc/config/io_helidon_webserver_cors_CorsFeature.adoc b/docs/src/main/asciidoc/config/io_helidon_webserver_cors_CorsFeature.adoc
index 9531104cc8..c57c843420 100644
--- a/docs/src/main/asciidoc/config/io_helidon_webserver_cors_CorsFeature.adoc
+++ b/docs/src/main/asciidoc/config/io_helidon_webserver_cors_CorsFeature.adoc
@@ -66,7 +66,7 @@ This type provides the following service implementations:
 |`sockets` |string[&#93; |{nbsp} |List of sockets to register this feature on. If empty, it would get registered on all sockets.
 
  Socket names to register on, defaults to empty (all available sockets)
-|`weight` |double |`950.0` |Weight of the CORS feature. As it is used by other features, the default is quite high:
+|`weight` |double |`850.0` |Weight of the CORS feature. As it is used by other features, the default is quite high:
  CorsFeature.WEIGHT.
 
  Weight of the feature
diff --git a/docs/src/main/asciidoc/config/io_helidon_webserver_grpc_GrpcConfig.adoc b/docs/src/main/asciidoc/config/io_helidon_webserver_grpc_GrpcConfig.adoc
index 5df77b1b50..87d5f506f0 100644
--- a/docs/src/main/asciidoc/config/io_helidon_webserver_grpc_GrpcConfig.adoc
+++ b/docs/src/main/asciidoc/config/io_helidon_webserver_grpc_GrpcConfig.adoc
@@ -1,6 +1,6 @@
 ///////////////////////////////////////////////////////////////////////////////
 
-    Copyright (c) 2023 Oracle and/or its affiliates.
+    Copyright (c) 2023, 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.
@@ -30,10 +30,16 @@ include::{rootdir}/includes/attributes.adoc[]
 Type: link:{javadoc-base-url}/io.helidon.webserver.grpc/io/helidon/webserver/grpc/GrpcConfig.html[io.helidon.webserver.grpc.GrpcConfig]
 
 
+[source,text]
+.Config key
+----
+grpc
+----
+
 
 This type provides the following service implementations:
 
-- `io.helidon.webserver.spi.ProtocolConfig`
+- `io.helidon.webserver.spi.ProtocolConfigProvider`
 
 
 == Configuration options
diff --git a/docs/src/main/asciidoc/config/io_helidon_webserver_http1_Http1Config.adoc b/docs/src/main/asciidoc/config/io_helidon_webserver_http1_Http1Config.adoc
index 7a8b58ea10..a72c8fbf80 100644
--- a/docs/src/main/asciidoc/config/io_helidon_webserver_http1_Http1Config.adoc
+++ b/docs/src/main/asciidoc/config/io_helidon_webserver_http1_Http1Config.adoc
@@ -30,10 +30,16 @@ include::{rootdir}/includes/attributes.adoc[]
 Type: link:{javadoc-base-url}/io.helidon.webserver.http1/io/helidon/webserver/http1/Http1Config.html[io.helidon.webserver.http1.Http1Config]
 
 
+[source,text]
+.Config key
+----
+http_1_1
+----
+
 
 This type provides the following service implementations:
 
-- `io.helidon.webserver.spi.ProtocolConfig`
+- `io.helidon.webserver.spi.ProtocolConfigProvider`
 
 
 == Configuration options
diff --git a/docs/src/main/asciidoc/config/io_helidon_webserver_http2_Http2Config.adoc b/docs/src/main/asciidoc/config/io_helidon_webserver_http2_Http2Config.adoc
index c6324a3b99..099e63003e 100644
--- a/docs/src/main/asciidoc/config/io_helidon_webserver_http2_Http2Config.adoc
+++ b/docs/src/main/asciidoc/config/io_helidon_webserver_http2_Http2Config.adoc
@@ -30,10 +30,16 @@ include::{rootdir}/includes/attributes.adoc[]
 Type: link:{javadoc-base-url}/io.helidon.webserver.http2/io/helidon/webserver/http2/Http2Config.html[io.helidon.webserver.http2.Http2Config]
 
 
+[source,text]
+.Config key
+----
+http_2
+----
+
 
 This type provides the following service implementations:
 
-- `io.helidon.webserver.spi.ProtocolConfig`
+- `io.helidon.webserver.spi.ProtocolConfigProvider`
 
 
 == Configuration options
diff --git a/docs/src/main/asciidoc/config/io_helidon_webserver_observe_ObserveFeature.adoc b/docs/src/main/asciidoc/config/io_helidon_webserver_observe_ObserveFeature.adoc
index 16f38a02e3..2559f02848 100644
--- a/docs/src/main/asciidoc/config/io_helidon_webserver_observe_ObserveFeature.adoc
+++ b/docs/src/main/asciidoc/config/io_helidon_webserver_observe_ObserveFeature.adoc
@@ -70,10 +70,10 @@ This type provides the following service implementations:
 
 Such as:
 
- - xref:{rootdir}/config/io_helidon_webserver_observe_log_LogObserver.adoc[LogObserver]
- - xref:{rootdir}/config/io_helidon_webserver_observe_tracing_TracingObserver.adoc[TracingObserver]
- - xref:{rootdir}/config/io_helidon_webserver_observe_config_ConfigObserver.adoc[ConfigObserver]
- - xref:{rootdir}/config/io_helidon_webserver_observe_info_InfoObserver.adoc[InfoObserver]
+ - xref:{rootdir}/config/io_helidon_webserver_observe_log_LogObserver.adoc[log (LogObserver)]
+ - xref:{rootdir}/config/io_helidon_webserver_observe_tracing_TracingObserver.adoc[tracing (TracingObserver)]
+ - xref:{rootdir}/config/io_helidon_webserver_observe_config_ConfigObserver.adoc[config (ConfigObserver)]
+ - xref:{rootdir}/config/io_helidon_webserver_observe_info_InfoObserver.adoc[info (InfoObserver)]
  - xref:{rootdir}/config/io_helidon_webserver_observe_metrics_MetricsObserver.adoc[metrics (MetricsObserver)]
  - xref:{rootdir}/config/io_helidon_webserver_observe_health_HealthObserver.adoc[health (HealthObserver)]
 
diff --git a/docs/src/main/asciidoc/config/io_helidon_webserver_observe_config_ConfigObserver.adoc b/docs/src/main/asciidoc/config/io_helidon_webserver_observe_config_ConfigObserver.adoc
index 43eb920fc7..ed70f80669 100644
--- a/docs/src/main/asciidoc/config/io_helidon_webserver_observe_config_ConfigObserver.adoc
+++ b/docs/src/main/asciidoc/config/io_helidon_webserver_observe_config_ConfigObserver.adoc
@@ -30,6 +30,12 @@ include::{rootdir}/includes/attributes.adoc[]
 Type: link:{javadoc-base-url}/io.helidon.webserver.observe.config/io/helidon/webserver/observe/config/ConfigObserver.html[io.helidon.webserver.observe.config.ConfigObserver]
 
 
+[source,text]
+.Config key
+----
+config
+----
+
 
 This type provides the following service implementations:
 
diff --git a/docs/src/main/asciidoc/config/io_helidon_webserver_observe_info_InfoObserver.adoc b/docs/src/main/asciidoc/config/io_helidon_webserver_observe_info_InfoObserver.adoc
index 4016a71d1a..e56c148a15 100644
--- a/docs/src/main/asciidoc/config/io_helidon_webserver_observe_info_InfoObserver.adoc
+++ b/docs/src/main/asciidoc/config/io_helidon_webserver_observe_info_InfoObserver.adoc
@@ -30,6 +30,12 @@ include::{rootdir}/includes/attributes.adoc[]
 Type: link:{javadoc-base-url}/io.helidon.webserver.observe.info/io/helidon/webserver/observe/info/InfoObserver.html[io.helidon.webserver.observe.info.InfoObserver]
 
 
+[source,text]
+.Config key
+----
+info
+----
+
 
 This type provides the following service implementations:
 
diff --git a/docs/src/main/asciidoc/config/io_helidon_webserver_observe_log_LogObserver.adoc b/docs/src/main/asciidoc/config/io_helidon_webserver_observe_log_LogObserver.adoc
index 69402e92a7..ff1d897bf0 100644
--- a/docs/src/main/asciidoc/config/io_helidon_webserver_observe_log_LogObserver.adoc
+++ b/docs/src/main/asciidoc/config/io_helidon_webserver_observe_log_LogObserver.adoc
@@ -30,6 +30,12 @@ include::{rootdir}/includes/attributes.adoc[]
 Type: link:{javadoc-base-url}/io.helidon.webserver.observe.log/io/helidon/webserver/observe/log/LogObserver.html[io.helidon.webserver.observe.log.LogObserver]
 
 
+[source,text]
+.Config key
+----
+log
+----
+
 
 This type provides the following service implementations:
 
diff --git a/docs/src/main/asciidoc/config/io_helidon_webserver_observe_tracing_TracingObserver.adoc b/docs/src/main/asciidoc/config/io_helidon_webserver_observe_tracing_TracingObserver.adoc
index fc0e267850..feea2ca6ab 100644
--- a/docs/src/main/asciidoc/config/io_helidon_webserver_observe_tracing_TracingObserver.adoc
+++ b/docs/src/main/asciidoc/config/io_helidon_webserver_observe_tracing_TracingObserver.adoc
@@ -30,6 +30,12 @@ include::{rootdir}/includes/attributes.adoc[]
 Type: link:{javadoc-base-url}/io.helidon.webserver.observe.tracing/io/helidon/webserver/observe/tracing/TracingObserver.html[io.helidon.webserver.observe.tracing.TracingObserver]
 
 
+[source,text]
+.Config key
+----
+tracing
+----
+
 
 This type provides the following service implementations:
 
diff --git a/docs/src/main/asciidoc/config/io_helidon_webserver_websocket_WsConfig.adoc b/docs/src/main/asciidoc/config/io_helidon_webserver_websocket_WsConfig.adoc
index 884ee43c45..304bd316de 100644
--- a/docs/src/main/asciidoc/config/io_helidon_webserver_websocket_WsConfig.adoc
+++ b/docs/src/main/asciidoc/config/io_helidon_webserver_websocket_WsConfig.adoc
@@ -30,10 +30,16 @@ include::{rootdir}/includes/attributes.adoc[]
 Type: link:{javadoc-base-url}/io.helidon.webserver.websocket/io/helidon/webserver/websocket/WsConfig.html[io.helidon.webserver.websocket.WsConfig]
 
 
+[source,text]
+.Config key
+----
+websocket
+----
+
 
 This type provides the following service implementations:
 
-- `io.helidon.webserver.spi.ProtocolConfig`
+- `io.helidon.webserver.spi.ProtocolConfigProvider`
 
 
 == Configuration options
diff --git a/webclient/api/src/main/java/io/helidon/webclient/api/ClientUri.java b/webclient/api/src/main/java/io/helidon/webclient/api/ClientUri.java
index 9903579584..c0f5403f52 100644
--- a/webclient/api/src/main/java/io/helidon/webclient/api/ClientUri.java
+++ b/webclient/api/src/main/java/io/helidon/webclient/api/ClientUri.java
@@ -18,7 +18,6 @@ package io.helidon.webclient.api;
 
 import java.net.URI;
 
-import io.helidon.common.uri.UriEncoding;
 import io.helidon.common.uri.UriFragment;
 import io.helidon.common.uri.UriInfo;
 import io.helidon.common.uri.UriPath;
@@ -194,11 +193,10 @@ public class ClientUri implements UriInfo {
 
         uriBuilder.path(resolvePath(uriBuilder.path().path(), uri.getPath()));
 
-        String queryString = uri.getQuery();
+        String queryString = uri.getRawQuery();
         if (queryString != null) {
             // class URI does not decode +'s, so we do it here
-            query.fromQueryString(queryString.indexOf('+') >= 0 ? UriEncoding.decodeUri(queryString)
-                    : queryString);
+            query.fromQueryString(queryString.replaceAll("\\+", "%20"));
         }
 
         if (uri.getRawFragment() != null) {
diff --git a/webserver/grpc/src/main/java/io/helidon/webserver/grpc/GrpcConfigBlueprint.java b/webserver/grpc/src/main/java/io/helidon/webserver/grpc/GrpcConfigBlueprint.java
index 286a8111be..0593ee88fa 100644
--- a/webserver/grpc/src/main/java/io/helidon/webserver/grpc/GrpcConfigBlueprint.java
+++ b/webserver/grpc/src/main/java/io/helidon/webserver/grpc/GrpcConfigBlueprint.java
@@ -18,10 +18,11 @@ package io.helidon.webserver.grpc;
 
 import io.helidon.builder.api.Prototype;
 import io.helidon.webserver.spi.ProtocolConfig;
+import io.helidon.webserver.spi.ProtocolConfigProvider;
 
 @Prototype.Blueprint
-@Prototype.Configured
-@Prototype.Provides(ProtocolConfig.class)
+@Prototype.Configured(root = false, value = GrpcProtocolProvider.CONFIG_NAME)
+@Prototype.Provides(ProtocolConfigProvider.class)
 interface GrpcConfigBlueprint extends ProtocolConfig {
     /**
      * Protocol configuration type.
diff --git a/webserver/http2/src/main/java/io/helidon/webserver/http2/Http2ConfigBlueprint.java b/webserver/http2/src/main/java/io/helidon/webserver/http2/Http2ConfigBlueprint.java
index b06301dbbd..d1fe59ed0c 100644
--- a/webserver/http2/src/main/java/io/helidon/webserver/http2/Http2ConfigBlueprint.java
+++ b/webserver/http2/src/main/java/io/helidon/webserver/http2/Http2ConfigBlueprint.java
@@ -22,13 +22,14 @@ import io.helidon.builder.api.Option;
 import io.helidon.builder.api.Prototype;
 import io.helidon.http.RequestedUriDiscoveryContext;
 import io.helidon.webserver.spi.ProtocolConfig;
+import io.helidon.webserver.spi.ProtocolConfigProvider;
 
 /**
  * HTTP/2 server configuration.
  */
 @Prototype.Blueprint(decorator = Http2ConfigBlueprint.Http2ConfigDecorator.class)
-@Prototype.Configured
-@Prototype.Provides(ProtocolConfig.class)
+@Prototype.Configured(root = false, value = Http2ConnectionProvider.CONFIG_NAME)
+@Prototype.Provides(ProtocolConfigProvider.class)
 interface Http2ConfigBlueprint extends ProtocolConfig {
     /**
      * The size of the largest frame payload that the sender is willing to receive in bytes.
diff --git a/webserver/observe/config/src/main/java/io/helidon/webserver/observe/config/ConfigObserverConfigBlueprint.java b/webserver/observe/config/src/main/java/io/helidon/webserver/observe/config/ConfigObserverConfigBlueprint.java
index 4c1f15c3bf..32bf09fba4 100644
--- a/webserver/observe/config/src/main/java/io/helidon/webserver/observe/config/ConfigObserverConfigBlueprint.java
+++ b/webserver/observe/config/src/main/java/io/helidon/webserver/observe/config/ConfigObserverConfigBlueprint.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2023, 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.
@@ -24,7 +24,7 @@ import io.helidon.webserver.observe.ObserverConfigBase;
 import io.helidon.webserver.observe.spi.ObserveProvider;
 
 @Prototype.Blueprint
-@Prototype.Configured
+@Prototype.Configured(root = false, value = "config")
 @Prototype.Provides(ObserveProvider.class)
 interface ConfigObserverConfigBlueprint extends ObserverConfigBase, Prototype.Factory<ConfigObserver> {
     @Option.Configured
diff --git a/webserver/observe/info/src/main/java/io/helidon/webserver/observe/info/InfoObserverConfigBlueprint.java b/webserver/observe/info/src/main/java/io/helidon/webserver/observe/info/InfoObserverConfigBlueprint.java
index f6f8a2f4e7..7f66920251 100644
--- a/webserver/observe/info/src/main/java/io/helidon/webserver/observe/info/InfoObserverConfigBlueprint.java
+++ b/webserver/observe/info/src/main/java/io/helidon/webserver/observe/info/InfoObserverConfigBlueprint.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2023, 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.
@@ -27,7 +27,7 @@ import io.helidon.webserver.observe.spi.ObserveProvider;
  * Info Observer configuration.
  */
 @Prototype.Blueprint
-@Prototype.Configured
+@Prototype.Configured(root = false, value = "info")
 @Prototype.Provides(ObserveProvider.class)
 interface InfoObserverConfigBlueprint extends ObserverConfigBase, Prototype.Factory<InfoObserver> {
     @Option.Configured
diff --git a/webserver/observe/log/src/main/java/io/helidon/webserver/observe/log/LogObserverConfigBlueprint.java b/webserver/observe/log/src/main/java/io/helidon/webserver/observe/log/LogObserverConfigBlueprint.java
index b91a384eab..fbb689e849 100644
--- a/webserver/observe/log/src/main/java/io/helidon/webserver/observe/log/LogObserverConfigBlueprint.java
+++ b/webserver/observe/log/src/main/java/io/helidon/webserver/observe/log/LogObserverConfigBlueprint.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2023, 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.
@@ -25,7 +25,7 @@ import io.helidon.webserver.observe.spi.ObserveProvider;
  * Log Observer configuration.
  */
 @Prototype.Blueprint
-@Prototype.Configured
+@Prototype.Configured(root = false, value = "log")
 @Prototype.Provides(ObserveProvider.class)
 interface LogObserverConfigBlueprint extends ObserverConfigBase, Prototype.Factory<LogObserver> {
     @Option.Configured
diff --git a/webserver/observe/tracing/pom.xml b/webserver/observe/tracing/pom.xml
index 355ea4a973..283e5266bc 100644
--- a/webserver/observe/tracing/pom.xml
+++ b/webserver/observe/tracing/pom.xml
@@ -50,6 +50,11 @@
             <artifactId>helidon-common-features-api</artifactId>
             <optional>true</optional>
         </dependency>
+        <dependency>
+            <groupId>io.helidon.config</groupId>
+            <artifactId>helidon-config-metadata</artifactId>
+            <optional>true</optional>
+        </dependency>
         <dependency>
             <groupId>io.helidon.webserver.testing.junit5</groupId>
             <artifactId>helidon-webserver-testing-junit5</artifactId>
diff --git a/webserver/observe/tracing/src/main/java/io/helidon/webserver/observe/tracing/TracingObserverConfigBlueprint.java b/webserver/observe/tracing/src/main/java/io/helidon/webserver/observe/tracing/TracingObserverConfigBlueprint.java
index b2f1c5cef6..b7d3a3ba08 100644
--- a/webserver/observe/tracing/src/main/java/io/helidon/webserver/observe/tracing/TracingObserverConfigBlueprint.java
+++ b/webserver/observe/tracing/src/main/java/io/helidon/webserver/observe/tracing/TracingObserverConfigBlueprint.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Oracle and/or its affiliates.
+ * Copyright (c) 2023, 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.
@@ -33,7 +33,7 @@ import io.helidon.webserver.observe.spi.ObserveProvider;
  * @see io.helidon.webserver.observe.tracing.TracingObserver#builder()
  */
 @Prototype.Blueprint(decorator = TracingObserverSupport.TracingObserverDecorator.class)
-@Prototype.Configured
+@Prototype.Configured(root = false, value = "tracing")
 @Prototype.Provides(ObserveProvider.class)
 interface TracingObserverConfigBlueprint extends ObserverConfigBase, Prototype.Factory<TracingObserver> {
     @Option.Default("tracing")
@@ -60,6 +60,7 @@ interface TracingObserverConfigBlueprint extends ObserverConfigBase, Prototype.F
      *
      * By default we disable both the SE-style paths ({@code /observe/health}) and the MP-style paths ({@code /health}).
      */
+
     /**
      * Path specific configuration of tracing.
      *
diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/http1/Http1ConfigBlueprint.java b/webserver/webserver/src/main/java/io/helidon/webserver/http1/Http1ConfigBlueprint.java
index 93f38aa76b..f7673a98a2 100644
--- a/webserver/webserver/src/main/java/io/helidon/webserver/http1/Http1ConfigBlueprint.java
+++ b/webserver/webserver/src/main/java/io/helidon/webserver/http1/Http1ConfigBlueprint.java
@@ -22,13 +22,14 @@ import io.helidon.builder.api.Option;
 import io.helidon.builder.api.Prototype;
 import io.helidon.http.RequestedUriDiscoveryContext;
 import io.helidon.webserver.spi.ProtocolConfig;
+import io.helidon.webserver.spi.ProtocolConfigProvider;
 
 /**
  * HTTP/1.1 server configuration.
  */
 @Prototype.Blueprint(decorator = Http1BuilderDecorator.class)
-@Prototype.Configured
-@Prototype.Provides(ProtocolConfig.class)
+@Prototype.Configured(root = false, value = Http1ConnectionProvider.CONFIG_NAME)
+@Prototype.Provides(ProtocolConfigProvider.class)
 interface Http1ConfigBlueprint extends ProtocolConfig {
     /**
      * Name of this configuration, in most cases the same as {@link #type()}.
diff --git a/webserver/websocket/src/main/java/io/helidon/webserver/websocket/WsConfigBlueprint.java b/webserver/websocket/src/main/java/io/helidon/webserver/websocket/WsConfigBlueprint.java
index a354775fc3..29594b146e 100644
--- a/webserver/websocket/src/main/java/io/helidon/webserver/websocket/WsConfigBlueprint.java
+++ b/webserver/websocket/src/main/java/io/helidon/webserver/websocket/WsConfigBlueprint.java
@@ -21,13 +21,14 @@ import java.util.Set;
 import io.helidon.builder.api.Option;
 import io.helidon.builder.api.Prototype;
 import io.helidon.webserver.spi.ProtocolConfig;
+import io.helidon.webserver.spi.ProtocolConfigProvider;
 
 /**
  * WebSocket protocol configuration.
  */
 @Prototype.Blueprint
-@Prototype.Configured
-@Prototype.Provides(ProtocolConfig.class)
+@Prototype.Configured(root = false, value = WsUpgradeProvider.CONFIG_NAME)
+@Prototype.Provides(ProtocolConfigProvider.class)
 interface WsConfigBlueprint extends ProtocolConfig {
     /**
      * WebSocket origins.


Test patch:
diff --git a/builder/tests/codegen/src/test/java/io/helidon/builder/codegen/TypesTest.java b/builder/tests/codegen/src/test/java/io/helidon/builder/codegen/TypesTest.java
index f5c1d35db9..f0d2714df0 100644
--- a/builder/tests/codegen/src/test/java/io/helidon/builder/codegen/TypesTest.java
+++ b/builder/tests/codegen/src/test/java/io/helidon/builder/codegen/TypesTest.java
@@ -94,6 +94,7 @@ public class TypesTest {
         checkField(toCheck, checked, fields, "PROTOTYPE_ANNOTATED", Prototype.Annotated.class);
         checkField(toCheck, checked, fields, "PROTOTYPE_FACTORY", Prototype.Factory.class);
         checkField(toCheck, checked, fields, "PROTOTYPE_CONFIGURED", Prototype.Configured.class);
+        checkField(toCheck, checked, fields, "PROTOTYPE_PROVIDES", Prototype.Provides.class);
         checkField(toCheck, checked, fields, "PROTOTYPE_BUILDER", Prototype.Builder.class);
         checkField(toCheck, checked, fields, "PROTOTYPE_CONFIGURED_BUILDER", Prototype.ConfiguredBuilder.class);
         checkField(toCheck, checked, fields, "PROTOTYPE_CUSTOM_METHODS", Prototype.CustomMethods.class);
diff --git a/common/uri/src/test/java/io/helidon/common/uri/UriQueryTest.java b/common/uri/src/test/java/io/helidon/common/uri/UriQueryTest.java
index edb01dd62b..d9dc16fed2 100644
--- a/common/uri/src/test/java/io/helidon/common/uri/UriQueryTest.java
+++ b/common/uri/src/test/java/io/helidon/common/uri/UriQueryTest.java
@@ -57,7 +57,7 @@ class UriQueryTest {
         assertThat(uriQuery.get("h"), is("xc#e<"));
         assertThat(uriQuery.get("a"), is("b&c=d"));
     }
-    
+
     @Test
     void testEmptyQueryString() {
         UriQuery uriQuery = UriQuery.create("");
@@ -80,10 +80,23 @@ class UriQueryTest {
         UriQuery uriQuery = UriQuery.create(URI.create("http://foo/bar?a&b=c"));
         OptionalValue<String> optional = uriQuery.first("a");
         assertThat(optional.isEmpty(), is(true));
-        
+
         assertThat(uriQuery.all("a"), hasItems());
         assertThat(uriQuery.all("b"), hasItems("c"));
         assertThat(uriQuery.getRaw("a"), is(""));
     }
 
+    @Test
+    void testFromQueryString() {
+        UriQueryWriteable query = UriQueryWriteable.create();
+        query.fromQueryString("p1=v1&p2=v2&p3=%2F%2Fv3%2F%2F&p4=a%20b%20c");
+        assertThat(query.get("p1"), is("v1"));
+        assertThat(query.get("p2"), is("v2"));
+        assertThat(query.get("p3"), is("//v3//"));
+        // make sure the encoded value is correct
+        assertThat(query.getRaw("p3"), is("%2F%2Fv3%2F%2F"));
+        assertThat(query.get("p4"), is("a b c"));
+        assertThat(query.getRaw("p4"), is("a%20b%20c"));
+    }
+
 }
\ No newline at end of file
diff --git a/webclient/api/src/test/java/io/helidon/webclient/api/ClientUriTest.java b/webclient/api/src/test/java/io/helidon/webclient/api/ClientUriTest.java
index 56c29a00d7..a670bc4558 100644
--- a/webclient/api/src/test/java/io/helidon/webclient/api/ClientUriTest.java
+++ b/webclient/api/src/test/java/io/helidon/webclient/api/ClientUriTest.java
@@ -20,8 +20,10 @@ import java.net.URI;
 
 import io.helidon.common.uri.UriPath;
 import io.helidon.common.uri.UriQueryWriteable;
+
 import org.junit.jupiter.api.Test;
 
+import static org.hamcrest.CoreMatchers.hasItems;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -63,7 +65,12 @@ class ClientUriTest {
     void testQueryParams() {
         UriQueryWriteable query = UriQueryWriteable.create();
         query.fromQueryString("p1=v1&p2=v2&p3=%2F%2Fv3%2F%2F");
-        ClientUri helper = ClientUri.create(URI.create("http://localhost:8080/loom/quick?" + query.value()));
+        assertThat(query.get("p1"), is("v1"));
+        assertThat(query.get("p2"), is("v2"));
+        assertThat(query.get("p3"), is("//v3//"));
+        assertThat(query.getRaw("p3"), is("%2F%2Fv3%2F%2F"));
+
+        ClientUri helper = ClientUri.create(URI.create("http://localhost:8080/loom/quick?" + query.rawValue()));
 
         assertThat(helper.authority(), is("localhost:8080"));
         assertThat(helper.host(), is("localhost"));
@@ -76,6 +83,13 @@ class ClientUriTest {
         assertThat(helper.query().getRaw("p3"), is("%2F%2Fv3%2F%2F"));
     }
 
+    @Test
+    void testQueryMultipleValues() {
+        UriQueryWriteable query = UriQueryWriteable.create();
+        query.fromQueryString("p1=v1&p1=v2");
+        assertThat(query.all("p1"), hasItems("v1", "v2"));
+    }
+
     @Test
     void testResolvePath() {
         ClientUri helper = ClientUri.create(URI.create("http://localhost:8080/loom"));