This is an automated email from the ASF dual-hosted git repository.

apolovtsev pushed a commit to branch ignite-3.0
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/ignite-3.0 by this push:
     new 8bfe47f2cf IGNITE-24014 Introduce a way to have key-value entries in 
configuration (#4997)
8bfe47f2cf is described below

commit 8bfe47f2cfbae272c95d060a54004909da33b3d1
Author: Alexander Polovtcev <[email protected]>
AuthorDate: Fri Jan 10 17:24:32 2025 +0200

    IGNITE-24014 Introduce a way to have key-value entries in configuration 
(#4997)
    
    (cherry picked from commit 7eb75ec7088ac3bcdb9e9cc8e8b1aea5b632612d)
---
 examples/config/ignite-config.conf                 |   4 +-
 .../management/topology/ItLogicalTopologyTest.java |   2 +-
 .../NodeAttributeConfigurationSchema.java          |   4 +-
 .../processor/ItConfigurationProcessorTest.java    |  60 +++++++
 .../MultipleInjectedValuesConfigurationSchema.java |  30 ++++
 .../UnsupportedFieldTypeConfigurationSchema.java   |  27 +++
 .../injectedvalue/ValidConfigurationSchema.java}   |  19 +-
 .../ValueAndInjectedValueConfigurationSchema.java} |  26 ++-
 .../processor/ConfigurationProcessor.java          |  71 ++++----
 .../processor/ConfigurationProcessorUtils.java     |  22 ++-
 .../validators/InjectedValueValidator.java         | 101 +++++++++++
 .../configuration/annotation/InjectedValue.java    |  79 +++++++++
 .../SystemPropertyConfigurationSchema.java         |   4 +-
 ...DistributedConfigurationPropertyHolderTest.java |   3 +-
 .../asm/ConfigurationImplAsmGenerator.java         |   3 +-
 .../configuration/asm/InnerNodeAsmGenerator.java   |  26 ++-
 .../hocon/HoconListConfigurationSource.java        |  44 ++---
 .../hocon/HoconObjectConfigurationSource.java      |  92 +++++-----
 .../hocon/HoconPrimitiveConfigurationSource.java   |  14 +-
 .../configuration/tree/ConstructableTreeNode.java  |   9 +
 .../configuration/tree/ConverterToMapVisitor.java  |  71 ++++++--
 .../configuration/util/ConfigurationUtil.java      |  23 ++-
 .../InjectedValueConfigurationTest.java            | 194 +++++++++++++++++++++
 .../configuration/hocon/HoconConverterTest.java    |   2 +-
 .../ItDistributionZonesFiltersTest.java            |  20 +--
 .../rebalance/ItRebalanceTriggersRecoveryTest.java |   4 +-
 .../BaseDistributionZoneManagerTest.java           |   4 +-
 modules/distribution-zones/tech-notes/filters.md   |  15 +-
 .../metastorage/TestMetasStorageUtils.java         |   4 +-
 ...aStorageCompactionTriggerConfigurationTest.java |   4 +-
 .../replicator/ItReplicaLifecycleTest.java         |   6 +-
 .../runner/app/ItIgniteNodeRestartTest.java        |   4 +-
 .../runner/app/ItReplicaStateManagerTest.java      |   6 +-
 .../sql/engine/ItUnstableTopologyTest.java         |   2 +-
 ...ilablePartitionsRecoveryByFilterUpdateTest.java |   6 +-
 35 files changed, 793 insertions(+), 212 deletions(-)

diff --git a/examples/config/ignite-config.conf 
b/examples/config/ignite-config.conf
index adc047156a..6f95edcdcb 100644
--- a/examples/config/ignite-config.conf
+++ b/examples/config/ignite-config.conf
@@ -24,7 +24,7 @@ ignite {
         ]
     }
     nodeAttributes.nodeAttributes {
-        region.attribute = US
-        storage.attribute = SSD
+        region = US
+        storage = SSD
     }
 }
diff --git 
a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/topology/ItLogicalTopologyTest.java
 
b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/topology/ItLogicalTopologyTest.java
index 3a7593d302..97e3816272 100644
--- 
a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/topology/ItLogicalTopologyTest.java
+++ 
b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/topology/ItLogicalTopologyTest.java
@@ -67,7 +67,7 @@ class ItLogicalTopologyTest extends 
ClusterPerTestIntegrationTest {
             + "    port: {},\n"
             + "    nodeFinder.netClusterNodes: [ {} ]\n"
             + "  },\n"
-            + "  nodeAttributes.nodeAttributes: {region.attribute = US, 
storage.attribute = SSD},\n"
+            + "  nodeAttributes.nodeAttributes: {region = US, storage = 
SSD},\n"
             + "  storage.profiles: {lru_rocks.engine = rocksdb, 
segmented_aipersist.engine = aipersist},\n"
             + "  clientConnector.port: {},\n"
             + "  rest.port: {},\n"
diff --git 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
 
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
index cb52ebf3a2..3dc822dc4a 100644
--- 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
+++ 
b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
@@ -19,7 +19,7 @@ package 
org.apache.ignite.internal.cluster.management.configuration;
 
 import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.InjectedName;
-import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.annotation.InjectedValue;
 
 /**
  * Node's attribute configuration schema. User can specify any number of pairs 
(key, attribute) for a node through the local configuration
@@ -35,6 +35,6 @@ public class NodeAttributeConfigurationSchema {
     public String name;
 
     /** Node attribute field. */
-    @Value(hasDefault = true)
+    @InjectedValue(hasDefault = true)
     public String attribute = "";
 }
diff --git 
a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ItConfigurationProcessorTest.java
 
b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ItConfigurationProcessorTest.java
index b47f017e45..ce2f61768c 100644
--- 
a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ItConfigurationProcessorTest.java
+++ 
b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ItConfigurationProcessorTest.java
@@ -615,6 +615,66 @@ public class ItConfigurationProcessorTest extends 
AbstractProcessorTest {
         configRootConfigurationInterfaceContent.contains("extends " + 
getConfigurationInterfaceName(abstractRootConfigSchema).simpleName());
     }
 
+    @Test
+    void testSuccessInjectedValueFieldCodeGeneration() {
+        String packageName = 
"org.apache.ignite.internal.configuration.processor.injectedvalue";
+
+        ClassName cls0 = ClassName.get(packageName, 
"ValidConfigurationSchema");
+
+        BatchCompilation batchCompile = batchCompile(cls0);
+
+        
assertThat(batchCompile.getCompilationStatus()).succeededWithoutWarnings();
+
+        assertEquals(3, batchCompile.generated().size());
+
+        assertTrue(batchCompile.getBySchema(cls0).allGenerated());
+    }
+
+    @Test
+    void testMultipleInjectedValuesUnsuccessfulGeneration() {
+        String packageName = 
"org.apache.ignite.internal.configuration.processor.injectedvalue";
+
+        ClassName cls0 = ClassName.get(packageName, 
"MultipleInjectedValuesConfigurationSchema");
+
+        assertThrowsEx(
+                IllegalStateException.class,
+                () -> batchCompile(cls0),
+                "Field marked as @InjectedValue must be the only \"value\" 
field in the schema "
+                        + 
"org.apache.ignite.internal.configuration.processor.injectedvalue.MultipleInjectedValuesConfigurationSchema,
 "
+                        + "found: [firstValue, secondValue]"
+        );
+    }
+
+    @Test
+    void testValuesAndInjectedValueUnsuccessfulGeneration() {
+        String packageName = 
"org.apache.ignite.internal.configuration.processor.injectedvalue";
+
+        ClassName cls0 = ClassName.get(packageName, 
"ValueAndInjectedValueConfigurationSchema");
+
+        assertThrowsEx(
+                IllegalStateException.class,
+                () -> batchCompile(cls0),
+                "Field marked as @InjectedValue must be the only \"value\" 
field in the schema "
+                        + 
"org.apache.ignite.internal.configuration.processor.injectedvalue.ValueAndInjectedValueConfigurationSchema,
 "
+                        + "found: [secondValue, firstValue, thirdValue]"
+        );
+    }
+
+    @Test
+    void testUnsupportedFieldTypeUnsuccessfulGeneration() {
+        String packageName = 
"org.apache.ignite.internal.configuration.processor.injectedvalue";
+
+        ClassName cls0 = ClassName.get(packageName, 
"UnsupportedFieldTypeConfigurationSchema");
+
+        assertThrowsEx(
+                IllegalStateException.class,
+                () -> batchCompile(cls0),
+                
"org.apache.ignite.internal.configuration.processor.injectedvalue.UnsupportedFieldTypeConfigurationSchema.firstValue
 "
+                        + "field must have one of the following types: 
boolean, int, long, double, String, UUID "
+                        + "or an array of aforementioned type."
+        );
+    }
+
     /**
      * Compile set of classes.
      *
diff --git 
a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/MultipleInjectedValuesConfigurationSchema.java
 
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/MultipleInjectedValuesConfigurationSchema.java
new file mode 100644
index 0000000000..02d7994e83
--- /dev/null
+++ 
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/MultipleInjectedValuesConfigurationSchema.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.configuration.processor.injectedvalue;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.InjectedValue;
+
+@Config
+public class MultipleInjectedValuesConfigurationSchema {
+    @InjectedValue
+    public String firstValue;
+
+    @InjectedValue
+    public String secondValue;
+}
diff --git 
a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/UnsupportedFieldTypeConfigurationSchema.java
 
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/UnsupportedFieldTypeConfigurationSchema.java
new file mode 100644
index 0000000000..e869d08f03
--- /dev/null
+++ 
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/UnsupportedFieldTypeConfigurationSchema.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.configuration.processor.injectedvalue;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.InjectedValue;
+
+@Config
+public class UnsupportedFieldTypeConfigurationSchema {
+    @InjectedValue
+    public Object firstValue;
+}
diff --git 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
 
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/ValidConfigurationSchema.java
similarity index 54%
copy from 
modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
copy to 
modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/ValidConfigurationSchema.java
index cb52ebf3a2..0698a0768a 100644
--- 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
+++ 
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/ValidConfigurationSchema.java
@@ -15,26 +15,17 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.cluster.management.configuration;
+package org.apache.ignite.internal.configuration.processor.injectedvalue;
 
 import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.InjectedName;
-import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.annotation.InjectedValue;
 
-/**
- * Node's attribute configuration schema. User can specify any number of pairs 
(key, attribute) for a node through the local configuration
- * of a node, and also can specify a filter through the SQL syntax of 
distribution zones. Any time data nodes will be recalculated,
- * corresponding filter will be applied to set of nodes with pre-defined 
attributes.
- *
- * @see <a 
href="https://github.com/apache/ignite-3/blob/main/modules/distribution-zones/tech-notes/filters.md";>Filter
 documentation</a>
- */
 @Config
-public class NodeAttributeConfigurationSchema {
-    /** Name of the node attribute. */
+public class ValidConfigurationSchema {
     @InjectedName
     public String name;
 
-    /** Node attribute field. */
-    @Value(hasDefault = true)
-    public String attribute = "";
+    @InjectedValue
+    public String someValue;
 }
diff --git 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
 
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/ValueAndInjectedValueConfigurationSchema.java
similarity index 51%
copy from 
modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
copy to 
modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/ValueAndInjectedValueConfigurationSchema.java
index cb52ebf3a2..f1e42d0356 100644
--- 
a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/configuration/NodeAttributeConfigurationSchema.java
+++ 
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/injectedvalue/ValueAndInjectedValueConfigurationSchema.java
@@ -15,26 +15,20 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.cluster.management.configuration;
+package org.apache.ignite.internal.configuration.processor.injectedvalue;
 
 import org.apache.ignite.configuration.annotation.Config;
-import org.apache.ignite.configuration.annotation.InjectedName;
+import org.apache.ignite.configuration.annotation.InjectedValue;
 import org.apache.ignite.configuration.annotation.Value;
 
-/**
- * Node's attribute configuration schema. User can specify any number of pairs 
(key, attribute) for a node through the local configuration
- * of a node, and also can specify a filter through the SQL syntax of 
distribution zones. Any time data nodes will be recalculated,
- * corresponding filter will be applied to set of nodes with pre-defined 
attributes.
- *
- * @see <a 
href="https://github.com/apache/ignite-3/blob/main/modules/distribution-zones/tech-notes/filters.md";>Filter
 documentation</a>
- */
 @Config
-public class NodeAttributeConfigurationSchema {
-    /** Name of the node attribute. */
-    @InjectedName
-    public String name;
+public class ValueAndInjectedValueConfigurationSchema {
+    @Value
+    public String firstValue;
+
+    @InjectedValue
+    public String secondValue;
 
-    /** Node attribute field. */
-    @Value(hasDefault = true)
-    public String attribute = "";
+    @Value
+    public String thirdValue;
 }
diff --git 
a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessor.java
 
b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessor.java
index 76e630a6b7..3dc7f7007a 100644
--- 
a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessor.java
+++ 
b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessor.java
@@ -24,6 +24,7 @@ import static javax.lang.model.element.Modifier.FINAL;
 import static javax.lang.model.element.Modifier.PUBLIC;
 import static javax.lang.model.element.Modifier.STATIC;
 import static 
org.apache.ignite.internal.configuration.processor.ConfigurationProcessorUtils.collectFieldsWithAnnotation;
+import static 
org.apache.ignite.internal.configuration.processor.ConfigurationProcessorUtils.containsAnyAnnotation;
 import static 
org.apache.ignite.internal.configuration.processor.ConfigurationProcessorUtils.findFirstPresentAnnotation;
 import static 
org.apache.ignite.internal.configuration.processor.ConfigurationProcessorUtils.getChangeName;
 import static 
org.apache.ignite.internal.configuration.processor.ConfigurationProcessorUtils.getConfigurationInterfaceName;
@@ -80,6 +81,7 @@ import org.apache.ignite.configuration.annotation.ConfigValue;
 import org.apache.ignite.configuration.annotation.ConfigurationExtension;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.annotation.InjectedName;
+import org.apache.ignite.configuration.annotation.InjectedValue;
 import org.apache.ignite.configuration.annotation.InternalId;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.PolymorphicConfig;
@@ -87,6 +89,7 @@ import 
org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
 import org.apache.ignite.configuration.annotation.PolymorphicId;
 import org.apache.ignite.configuration.annotation.Secret;
 import org.apache.ignite.configuration.annotation.Value;
+import 
org.apache.ignite.internal.configuration.processor.validators.InjectedValueValidator;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -153,12 +156,16 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
             return false;
         }
 
+        var injectedValueValidator = new InjectedValueValidator(processingEnv);
+
         for (TypeElement clazz : annotatedConfigs) {
             // Find all the fields of the schema.
             List<VariableElement> fields = fields(clazz);
 
             validateConfigurationSchemaClass(clazz, fields);
 
+            injectedValueValidator.validate(clazz, fields);
+
             // Get package name of the schema class
             String packageName = 
elementUtils.getPackageOf(clazz).getQualifiedName().toString();
 
@@ -175,10 +182,7 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
                     throw new ConfigurationProcessorException("Field " + 
clazz.getQualifiedName() + "." + field + " must be public");
                 }
 
-                final String fieldName = field.getSimpleName().toString();
-
-                // Get configuration types (VIEW, CHANGE and so on)
-                final TypeName interfaceGetMethodType = 
getInterfaceGetMethodType(field);
+                String fieldName = field.getSimpleName().toString();
 
                 if (field.getAnnotation(ConfigValue.class) != null) {
                     checkConfigField(field, ConfigValue.class);
@@ -190,8 +194,7 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
                     checkConfigField(field, NamedConfigValue.class);
                 }
 
-                Value valueAnnotation = field.getAnnotation(Value.class);
-                if (valueAnnotation != null) {
+                if (field.getAnnotation(Value.class) != null) {
                     // Must be a primitive or an array of the primitives 
(including java.lang.String, java.util.UUID).
                     if (!isValidValueAnnotationFieldType(field.asType())) {
                         throw new 
ConfigurationProcessorException(String.format(
@@ -204,8 +207,7 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
                     }
                 }
 
-                PolymorphicId polymorphicId = 
field.getAnnotation(PolymorphicId.class);
-                if (polymorphicId != null) {
+                if (field.getAnnotation(PolymorphicId.class) != null) {
                     if (!isClass(field.asType(), String.class)) {
                         throw new 
ConfigurationProcessorException(String.format(
                                 FIELD_MUST_BE_SPECIFIC_CLASS_ERROR_FORMAT,
@@ -237,6 +239,9 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
                     }
                 }
 
+                // Get configuration types (VIEW, CHANGE and so on)
+                TypeName interfaceGetMethodType = 
getInterfaceGetMethodType(field);
+
                 createGetters(configurationInterfaceBuilder, fieldName, 
interfaceGetMethodType);
             }
 
@@ -355,13 +360,11 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
      * @return Bundle with all types for configuration
      */
     private static TypeName getInterfaceGetMethodType(VariableElement field) {
-        TypeName interfaceGetMethodType = null;
-
         TypeName baseType = TypeName.get(field.asType());
 
         ConfigValue confAnnotation = field.getAnnotation(ConfigValue.class);
         if (confAnnotation != null) {
-            interfaceGetMethodType = getConfigurationInterfaceName((ClassName) 
baseType);
+            return getConfigurationInterfaceName((ClassName) baseType);
         }
 
         NamedConfigValue namedConfigAnnotation = 
field.getAnnotation(NamedConfigValue.class);
@@ -371,7 +374,7 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
             TypeName viewClassType = getViewName((ClassName) baseType);
             TypeName changeClassType = getChangeName((ClassName) baseType);
 
-            interfaceGetMethodType = ParameterizedTypeName.get(
+            return ParameterizedTypeName.get(
                     ClassName.get(NamedConfigurationTree.class),
                     interfaceGetType,
                     viewClassType,
@@ -379,13 +382,16 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
             );
         }
 
-        Value valueAnnotation = field.getAnnotation(Value.class);
-        PolymorphicId polymorphicIdAnnotation = 
field.getAnnotation(PolymorphicId.class);
-        InjectedName injectedNameAnnotation = 
field.getAnnotation(InjectedName.class);
-        InternalId internalIdAnnotation = 
field.getAnnotation(InternalId.class);
+        boolean containsAnnotation = containsAnyAnnotation(
+                field,
+                Value.class,
+                PolymorphicId.class,
+                InjectedName.class,
+                InternalId.class,
+                InjectedValue.class
+        );
 
-        if (valueAnnotation != null || polymorphicIdAnnotation != null || 
injectedNameAnnotation != null
-                || internalIdAnnotation != null) {
+        if (containsAnnotation) {
             // It is necessary to use class names without loading classes so 
that we won't
             // accidentally get NoClassDefFoundError
             ClassName confValueClass = 
ClassName.get("org.apache.ignite.configuration", "ConfigurationValue");
@@ -396,10 +402,10 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
                 genericType = genericType.box();
             }
 
-            interfaceGetMethodType = ParameterizedTypeName.get(confValueClass, 
genericType);
+            return ParameterizedTypeName.get(confValueClass, genericType);
         }
 
-        return interfaceGetMethodType;
+        throw new IllegalArgumentException(String.format("Field \"%s\" does 
not contain any supported annotations", field));
     }
 
     /**
@@ -503,8 +509,6 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
         ClassName consumerClsName = ClassName.get(Consumer.class);
 
         for (VariableElement field : fields) {
-            Value valAnnotation = field.getAnnotation(Value.class);
-
             String fieldName = field.getSimpleName().toString();
             TypeMirror schemaFieldType = field.asType();
             TypeName schemaFieldTypeName = TypeName.get(schemaFieldType);
@@ -512,15 +516,13 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
             boolean leafField = 
isValidValueAnnotationFieldType(schemaFieldType)
                     || !((ClassName) 
schemaFieldTypeName).simpleName().contains(CONFIGURATION_SCHEMA_POSTFIX);
 
-            boolean namedListField = 
field.getAnnotation(NamedConfigValue.class) != null;
-
             TypeName viewFieldType =
                     leafField ? schemaFieldTypeName : getViewName((ClassName) 
schemaFieldTypeName);
 
             TypeName changeFieldType =
                     leafField ? schemaFieldTypeName : 
getChangeName((ClassName) schemaFieldTypeName);
 
-            if (namedListField) {
+            if (field.getAnnotation(NamedConfigValue.class) != null) {
                 changeFieldType = ParameterizedTypeName.get(
                         ClassName.get(NamedListChange.class),
                         viewFieldType,
@@ -540,8 +542,7 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
             viewClsBuilder.addMethod(getMtdBuilder.build());
 
             // Read only.
-            if (field.getAnnotation(PolymorphicId.class) != null || 
field.getAnnotation(InjectedName.class) != null
-                    || field.getAnnotation(InternalId.class) != null) {
+            if (containsAnyAnnotation(field, PolymorphicId.class, 
InjectedName.class, InternalId.class)) {
                 continue;
             }
 
@@ -551,7 +552,7 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
                     .addModifiers(PUBLIC, ABSTRACT)
                     .returns(changeClsName);
 
-            if (valAnnotation != null) {
+            if (containsAnyAnnotation(field, Value.class, 
InjectedValue.class)) {
                 if (schemaFieldType.getKind() == TypeKind.ARRAY) {
                     changeMtdBuilder.varargs(true);
                 }
@@ -559,18 +560,16 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
                 changeMtdBuilder.addParameter(changeFieldType, fieldName);
             } else {
                 
changeMtdBuilder.addParameter(ParameterizedTypeName.get(consumerClsName, 
changeFieldType), fieldName);
-            }
 
-            changeClsBuilder.addMethod(changeMtdBuilder.build());
-
-            // Create "FooChange changeFoo()" method with no parameters, if 
it's a config value or named list value.
-            if (valAnnotation == null) {
+                // Create "FooChange changeFoo()" method with no parameters, 
if it's a config value or named list value.
                 MethodSpec.Builder shortChangeMtdBuilder = 
MethodSpec.methodBuilder(changeMtdName)
                         .addModifiers(PUBLIC, ABSTRACT)
                         .returns(changeFieldType);
 
                 changeClsBuilder.addMethod(shortChangeMtdBuilder.build());
             }
+
+            changeClsBuilder.addMethod(changeMtdBuilder.build());
         }
 
         if (isPolymorphicConfig) {
@@ -1112,8 +1111,8 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
      * Checks for missing {@link 
org.apache.ignite.configuration.annotation.Name} for nested schema with {@link 
InjectedName}.
      *
      * @param field Class field.
-     * @throws ConfigurationProcessorException If there is no {@link 
org.apache.ignite.configuration.annotation.Name} for the nested schema
-     *      with {@link InjectedName}.
+     * @throws ConfigurationProcessorException If there is no {@link 
org.apache.ignite.configuration.annotation.Name} for the nested
+     *         schema with {@link InjectedName}.
      */
     private void checkMissingNameForInjectedName(VariableElement field) {
         TypeElement fieldType = (TypeElement) 
processingEnv.getTypeUtils().asElement(field.asType());
@@ -1123,7 +1122,7 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
         List<VariableElement> fields;
 
         if (!isClass(superClassFieldType.asType(), Object.class)
-                && findFirstPresentAnnotation(superClassFieldType, 
AbstractConfiguration.class).isPresent()) {
+                && 
superClassFieldType.getAnnotation(AbstractConfiguration.class) != null) {
             fields = concat(
                     collectFieldsWithAnnotation(fields(fieldType), 
InjectedName.class),
                     collectFieldsWithAnnotation(fields(superClassFieldType), 
InjectedName.class)
diff --git 
a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessorUtils.java
 
b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessorUtils.java
index 5b3a2e7097..5061557ed1 100644
--- 
a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessorUtils.java
+++ 
b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessorUtils.java
@@ -27,13 +27,13 @@ import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.stream.Stream;
-import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.Element;
 import javax.lang.model.element.VariableElement;
 
 /**
  * Annotation processing utilities.
  */
-class ConfigurationProcessorUtils {
+public class ConfigurationProcessorUtils {
     /**
      * Returns {@link ClassName} for configuration class public interface.
      *
@@ -91,17 +91,25 @@ class ConfigurationProcessorUtils {
     }
 
     /**
-     * Returns the first annotation found for the class.
+     * Returns the first annotation found for the given element.
      *
-     * @param clazz Class type.
-     * @param annotationClasses Annotation classes that will be searched for 
the class.
+     * @param element Element.
+     * @param annotationClasses Annotation classes that will be searched for 
the element.
      */
     @SafeVarargs
     public static Optional<? extends Annotation> findFirstPresentAnnotation(
-            TypeElement clazz,
+            Element element,
             Class<? extends Annotation>... annotationClasses
     ) {
-        return 
Stream.of(annotationClasses).map(clazz::getAnnotation).filter(Objects::nonNull).findFirst();
+        return 
Stream.of(annotationClasses).map(element::getAnnotation).filter(Objects::nonNull).findFirst();
+    }
+
+    /**
+     * Returns {@code true} if any of the given annotations are present on the 
given element.
+     */
+    @SafeVarargs
+    public static boolean containsAnyAnnotation(Element element, Class<? 
extends Annotation>... annotationClasses) {
+        return findFirstPresentAnnotation(element, 
annotationClasses).isPresent();
     }
 
     /**
diff --git 
a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/validators/InjectedValueValidator.java
 
b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/validators/InjectedValueValidator.java
new file mode 100644
index 0000000000..7963ff8124
--- /dev/null
+++ 
b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/validators/InjectedValueValidator.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.configuration.processor.validators;
+
+import static 
org.apache.ignite.internal.configuration.processor.ConfigurationProcessorUtils.collectFieldsWithAnnotation;
+import static 
org.apache.ignite.internal.configuration.processor.ConfigurationProcessorUtils.simpleName;
+import static org.apache.ignite.internal.util.CollectionUtils.concat;
+
+import java.util.List;
+import java.util.UUID;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import org.apache.ignite.configuration.annotation.InjectedValue;
+import org.apache.ignite.configuration.annotation.Value;
+import 
org.apache.ignite.internal.configuration.processor.ConfigurationProcessorException;
+
+/**
+ * Validator class for the {@link InjectedValue} annotation.
+ */
+public class InjectedValueValidator {
+    private final ProcessingEnvironment processingEnv;
+
+    public InjectedValueValidator(ProcessingEnvironment processingEnv) {
+        this.processingEnv = processingEnv;
+    }
+
+    /**
+     * Validates invariants of the {@link InjectedValue} annotation. This 
includes:
+     *
+     * <ol>
+     *     <li>Type of InjectedValue field is either a primitive, or a String, 
or a UUID;</li>
+     *     <li>There is only a single InjectedValue field in the schema 
(including {@link Value} fields).</li>
+     * </ol>
+     */
+    public void validate(TypeElement clazz, List<VariableElement> fields) {
+        List<VariableElement> injectedValueFields = 
collectFieldsWithAnnotation(fields, InjectedValue.class);
+
+        if (injectedValueFields.isEmpty()) {
+            return;
+        }
+
+        List<VariableElement> valueFields = 
collectFieldsWithAnnotation(fields, Value.class);
+
+        if (injectedValueFields.size() > 1 || !valueFields.isEmpty()) {
+            throw new ConfigurationProcessorException(String.format(
+                    "Field marked as %s must be the only \"value\" field in 
the schema %s, found: %s",
+                    simpleName(InjectedValue.class),
+                    clazz.getQualifiedName(),
+                    concat(injectedValueFields, valueFields)
+            ));
+        }
+
+        VariableElement injectedValueField = injectedValueFields.get(0);
+
+        // Must be a primitive or an array of the primitives (including 
java.lang.String, java.util.UUID).
+        if (!isValidValueAnnotationFieldType(injectedValueField.asType())) {
+            throw new ConfigurationProcessorException(String.format(
+                    "%s.%s field must have one of the following types: "
+                            + "boolean, int, long, double, String, UUID or an 
array of aforementioned type.",
+                    clazz.getQualifiedName(),
+                    injectedValueField.getSimpleName()
+            ));
+        }
+    }
+
+    private boolean isValidValueAnnotationFieldType(TypeMirror type) {
+        if (type.getKind() == TypeKind.ARRAY) {
+            type = ((ArrayType) type).getComponentType();
+        }
+
+        return type.getKind().isPrimitive() || isClass(type, String.class) || 
isClass(type, UUID.class);
+    }
+
+    private boolean isClass(TypeMirror type, Class<?> clazz) {
+        TypeMirror classType = processingEnv
+                .getElementUtils()
+                .getTypeElement(clazz.getCanonicalName())
+                .asType();
+
+        return classType.equals(type);
+    }
+}
diff --git 
a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/InjectedValue.java
 
b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/InjectedValue.java
new file mode 100644
index 0000000000..403f100a8e
--- /dev/null
+++ 
b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/InjectedValue.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.configuration.annotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * This annotations is intended to be placed on {@link NamedConfigValue} 
elements to emulate key-value pairs in configuration schemas.
+ *
+ * <p>Therefore, this annotation induces the following constraints:
+ *
+ * <ol>
+ *     <li>Must be placed on a field of a configuration schema that represents 
a Named List element;</li>
+ *     <li>Must be the only field in the configuration schema, apart from the 
one marked with {@link InjectedName}.</li>
+ * </ol>
+ *
+ * <p>In all other aspects it behaves exactly like the {@link Value} 
annotation.
+ *
+ * <p>For example, this annotation can be used to declare a configuration 
schema with arbitrary {@code String} properties:
+ *
+ * <pre>{@code
+ *     @Config
+ *     class PropertyConfigurationSchema {
+ *         @NamedConfigValue
+ *         public PropertyEntryConfigurationSchema properties;
+ *     }
+ *
+ *     @Config
+ *     class PropertyEntryConfigurationSchema {
+ *         @InjectedName
+ *         public String propertyName;
+ *
+ *         @InjectedValue
+ *         public String propertyValue;
+ *     }
+ * }</pre>
+ *
+ * <p>This will allow to use the following HOCON to represent this 
configuration:
+ *
+ * <pre>{@code
+ *     root.properties {
+ *         property1: "value1",
+ *         property2: "value2",
+ *         property3: "value3"
+ *     }
+ * }</pre>
+ */
+@Target(FIELD)
+@Retention(RUNTIME)
+@Documented
+public @interface InjectedValue {
+    /**
+     * Indicates that the current configuration value has a default value. 
Value itself is derived from the instantiated object of a
+     * corresponding schema type. This means that the default is not 
necessarily a constant value.
+     *
+     * @return {@code hasDefault} flag value.
+     */
+    boolean hasDefault() default false;
+}
diff --git 
a/modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SystemPropertyConfigurationSchema.java
 
b/modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SystemPropertyConfigurationSchema.java
index acbe11a4e2..ad7a6c08c9 100644
--- 
a/modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SystemPropertyConfigurationSchema.java
+++ 
b/modules/configuration-system/src/main/java/org/apache/ignite/internal/configuration/SystemPropertyConfigurationSchema.java
@@ -20,7 +20,7 @@ package org.apache.ignite.internal.configuration;
 import org.apache.ignite.configuration.ConfigurationModule;
 import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.InjectedName;
-import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.annotation.InjectedValue;
 import org.apache.ignite.configuration.validation.CamelCaseKeys;
 import 
org.apache.ignite.internal.configuration.validation.LongNumberSystemPropertyValueValidator;
 
@@ -41,6 +41,6 @@ public class SystemPropertyConfigurationSchema {
     @InjectedName
     public String name;
 
-    @Value
+    @InjectedValue
     public String propertyValue;
 }
diff --git 
a/modules/configuration-system/src/test/java/org/apache/ignite/internal/configuration/utils/SystemDistributedConfigurationPropertyHolderTest.java
 
b/modules/configuration-system/src/test/java/org/apache/ignite/internal/configuration/utils/SystemDistributedConfigurationPropertyHolderTest.java
index b9a9b600a0..96d9929907 100644
--- 
a/modules/configuration-system/src/test/java/org/apache/ignite/internal/configuration/utils/SystemDistributedConfigurationPropertyHolderTest.java
+++ 
b/modules/configuration-system/src/test/java/org/apache/ignite/internal/configuration/utils/SystemDistributedConfigurationPropertyHolderTest.java
@@ -60,8 +60,7 @@ public class SystemDistributedConfigurationPropertyHolderTest 
extends BaseIgnite
 
     @Test
     void testValidSystemPropertiesOnStart(
-            @InjectConfiguration("mock.properties = {"
-                    + PROPERTY_NAME + ".propertyValue = \"newValue\"}")
+            @InjectConfiguration("mock.properties." + PROPERTY_NAME + " = 
newValue")
             SystemDistributedConfiguration systemConfig
     ) {
         var config = noopConfigHolder(systemConfig);
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationImplAsmGenerator.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationImplAsmGenerator.java
index a4a2d2fab4..8c0d45f8ef 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationImplAsmGenerator.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationImplAsmGenerator.java
@@ -54,6 +54,7 @@ import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.is
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicConfig;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicConfigInstance;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicId;
+import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isReadOnly;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isValue;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicInstanceId;
 import static org.apache.ignite.internal.util.ArrayUtils.nullOrEmpty;
@@ -385,7 +386,7 @@ class ConfigurationImplAsmGenerator extends 
AbstractAsmGenerator {
                         rootKeyVar,
                         changerVar,
                         listenOnlyVar,
-                        constantBoolean(isPolymorphicId(schemaField) || 
isInjectedName(schemaField) || isInternalId(schemaField))
+                        constantBoolean(isReadOnly(schemaField))
                 );
             } else {
                 SchemaClassesInfo fieldInfo = 
cgen.schemaInfo(schemaField.getType());
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/InnerNodeAsmGenerator.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/InnerNodeAsmGenerator.java
index 506a34b602..d1841df1a5 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/InnerNodeAsmGenerator.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/InnerNodeAsmGenerator.java
@@ -59,10 +59,12 @@ import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.co
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.hasDefault;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isConfigValue;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isInjectedName;
+import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isInjectedValue;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isNamedConfigValue;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicConfig;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicConfigInstance;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicId;
+import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isReadOnly;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.isValue;
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicInstanceId;
 import static org.apache.ignite.internal.util.CollectionUtils.concat;
@@ -177,6 +179,9 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
     /** {@link ConstructableTreeNode#construct(String, ConfigurationSource, 
boolean)} method name. */
     private static final String CONSTRUCT_MTD_NAME = "construct";
 
+    /** {@link ConstructableTreeNode#injectedValueFieldName}. */
+    private static final String INJECTED_VALUE_FIELD_NAME_MTD_NAME = 
"injectedValueFieldName";
+
     /** Mapping for each configuration {@link Field} to a static constant with 
this {@link Field} as value. */
     private final Map<Field, FieldDefinition> fieldToFieldDefinitionMap = new 
HashMap<>();
 
@@ -309,6 +314,9 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
         // Field with @InjectedName.
         FieldDefinition injectedNameFieldDef = null;
 
+        // Field with @InjectedValue.
+        Field injectedValueField = null;
+
         for (Field schemaField : concat(schemaFields, publicExtensionFields, 
internalExtensionFields, polymorphicFields)) {
             FieldDefinition fieldDef = addInnerNodeField(schemaField);
 
@@ -318,6 +326,8 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
                 polymorphicTypeIdFieldDef = fieldDef;
             } else if (isInjectedName(schemaField)) {
                 injectedNameFieldDef = fieldDef;
+            } else if (isInjectedValue(schemaField)) {
+                injectedValueField = schemaField;
             }
         }
 
@@ -444,6 +454,10 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
             addInjectedNameFieldMethods(injectedNameFieldDef);
         }
 
+        if (injectedValueField != null) {
+            implementInjectedValueFieldNameMethod(injectedValueField);
+        }
+
         if (polymorphicTypeIdFieldDef != null) {
             addIsPolymorphicMethod();
         }
@@ -1578,6 +1592,16 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator 
{
                 )).ret();
     }
 
+    private void implementInjectedValueFieldNameMethod(Field 
injectedValueField) {
+        MethodDefinition method = innerNodeClassDef.declareMethod(
+                EnumSet.of(PUBLIC),
+                INJECTED_VALUE_FIELD_NAME_MTD_NAME,
+                type(String.class)
+        );
+
+        
method.getBody().append(constantString(publicName(injectedValueField))).retObject();
+    }
+
     /**
      * Adds an override for the {@link InnerNode#isPolymorphic} method that 
returns {@code true}.
      */
@@ -1698,7 +1722,7 @@ class InnerNodeAsmGenerator extends AbstractAsmGenerator {
             );
 
             // Read only.
-            if (isPolymorphicId(schemaField) || isInjectedName(schemaField)) {
+            if (isReadOnly(schemaField)) {
                 continue;
             }
 
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconListConfigurationSource.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconListConfigurationSource.java
index bcae565b7c..e1c1486e9f 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconListConfigurationSource.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconListConfigurationSource.java
@@ -105,35 +105,39 @@ class HoconListConfigurationSource implements 
ConfigurationSource {
 
         String syntheticKeyName = ((NamedListNode<?>) node).syntheticKeyName();
 
-        int idx = 0;
-        for (Iterator<ConfigValue> iterator = hoconCfgList.iterator(); 
iterator.hasNext(); idx++) {
-            ConfigValue next = iterator.next();
+        for (int idx = 0; idx < hoconCfgList.size(); idx++) {
+            ConfigValue next = hoconCfgList.get(idx);
 
             if (next.valueType() != ConfigValueType.OBJECT) {
-                throw new IllegalArgumentException(
-                        format(
-                                "'%s' is expected to be a composite 
configuration node, not a single value",
-                                formatArrayPath(path, idx)
-                        )
-                );
+                throw new IllegalArgumentException(format(
+                        "'%s' is expected to be a composite configuration 
node, not a single value",
+                        formatArrayPath(path, idx)
+                ));
             }
 
             ConfigObject hoconCfg = (ConfigObject) next;
 
-            ConfigValue keyValue = hoconCfg.get(syntheticKeyName);
+            String key;
 
-            if (keyValue == null || keyValue.valueType() != 
ConfigValueType.STRING) {
-                throw new IllegalArgumentException(
-                        format(
-                                "'%s' configuration value is mandatory and 
must be a String",
-                                formatArrayPath(path, idx) + KEY_SEPARATOR + 
syntheticKeyName
-                        )
-                );
-            }
+            List<String> path;
 
-            String key = (String) keyValue.unwrapped();
+            ConfigValue keyValue = hoconCfg.get(syntheticKeyName);
 
-            List<String> path = appendKey(this.path, key);
+            if (keyValue != null && keyValue.valueType() == 
ConfigValueType.STRING) {
+                // If the synthetic key is present, check that it has the 
correct type and use it as the key.
+                key = (String) keyValue.unwrapped();
+                path = appendKey(this.path, key);
+            } else if (keyValue == null && hoconCfg.size() == 1) {
+                // If the synthetic key is not present explicitly, we need to 
handle the case when a configuration uses InjectedValue.
+                // This means that this object must only have one key.
+                key = hoconCfg.entrySet().iterator().next().getKey();
+                path = this.path;
+            } else {
+                throw new IllegalArgumentException(format(
+                        "'%s' configuration value is mandatory and must be a 
String",
+                        formatArrayPath(this.path, idx) + KEY_SEPARATOR + 
syntheticKeyName
+                ));
+            }
 
             node.construct(key, new 
HoconObjectConfigurationSource(syntheticKeyName, path, hoconCfg), false);
         }
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconObjectConfigurationSource.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconObjectConfigurationSource.java
index b6e97ec785..b6a3997860 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconObjectConfigurationSource.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconObjectConfigurationSource.java
@@ -27,7 +27,6 @@ import com.typesafe.config.ConfigObject;
 import com.typesafe.config.ConfigValue;
 import com.typesafe.config.ConfigValueType;
 import java.util.List;
-import java.util.Map;
 import java.util.NoSuchElementException;
 import 
org.apache.ignite.configuration.ConfigurationWrongPolymorphicTypeIdException;
 import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
@@ -67,76 +66,81 @@ class HoconObjectConfigurationSource implements 
ConfigurationSource {
         this.hoconCfgObject = hoconCfgObject;
     }
 
-    /** {@inheritDoc} */
     @Override
     public <T> T unwrap(Class<T> clazz) {
         throw wrongTypeException(clazz, path, -1);
     }
 
-    /** {@inheritDoc} */
     @Override
     public void descend(ConstructableTreeNode node) {
-        for (Map.Entry<String, ConfigValue> entry : hoconCfgObject.entrySet()) 
{
-            String key = entry.getKey();
+        String injectedValueFieldName = node.injectedValueFieldName();
 
-            if (key.equals(ignoredKey)) {
-                continue;
-            }
+        if (injectedValueFieldName == null) {
+            hoconCfgObject.forEach((key, value) -> parseConfigEntry(key, 
value, node));
+        } else {
+            assert hoconCfgObject.size() == 1; // User-friendly check must 
have been performed outside this method.
 
-            ConfigValue hoconCfgValue = entry.getValue();
+            ConfigValue value = hoconCfgObject.values().iterator().next();
 
-            try {
-                switch (hoconCfgValue.valueType()) {
-                    case NULL:
-                        node.construct(key, null, false);
+            parseConfigEntry(injectedValueFieldName, value, node);
+        }
+    }
 
-                        break;
+    private void parseConfigEntry(String key, ConfigValue hoconCfgValue, 
ConstructableTreeNode node) {
+        if (key.equals(ignoredKey)) {
+            return;
+        }
 
-                    case OBJECT: {
-                        List<String> path = appendKey(this.path, key);
+        try {
+            switch (hoconCfgValue.valueType()) {
+                case NULL:
+                    node.construct(key, null, false);
 
-                        node.construct(
-                                key,
-                                new HoconObjectConfigurationSource(null, path, 
(ConfigObject) hoconCfgValue),
-                                false
-                        );
+                    break;
 
-                        break;
-                    }
+                case OBJECT: {
+                    List<String> path = appendKey(this.path, key);
 
-                    case LIST: {
-                        List<String> path = appendKey(this.path, key);
+                    node.construct(
+                            key,
+                            new HoconObjectConfigurationSource(null, path, 
(ConfigObject) hoconCfgValue),
+                            false
+                    );
 
-                        node.construct(key, new 
HoconListConfigurationSource(path, (ConfigList) hoconCfgValue), false);
+                    break;
+                }
 
-                        break;
-                    }
+                case LIST: {
+                    List<String> path = appendKey(this.path, key);
 
-                    default: {
-                        List<String> path = appendKey(this.path, key);
+                    node.construct(key, new HoconListConfigurationSource(path, 
(ConfigList) hoconCfgValue), false);
 
-                        node.construct(key, new 
HoconPrimitiveConfigurationSource(path, hoconCfgValue), false);
-                    }
+                    break;
                 }
-            } catch (NoSuchElementException e) {
-                if (path.isEmpty()) {
-                    throw new IllegalArgumentException(
-                            format("'%s' configuration root doesn't exist", 
key), e
-                    );
-                } else {
-                    throw new IllegalArgumentException(
-                            format("'%s' configuration doesn't have the '%s' 
sub-configuration", join(path), key), e
-                    );
+
+                default: {
+                    List<String> path = appendKey(this.path, key);
+
+                    node.construct(key, new 
HoconPrimitiveConfigurationSource(path, hoconCfgValue), false);
                 }
-            } catch (ConfigurationWrongPolymorphicTypeIdException e) {
+            }
+        } catch (NoSuchElementException e) {
+            if (path.isEmpty()) {
                 throw new IllegalArgumentException(
-                        "Polymorphic configuration type is not correct: " + 
e.getMessage()
+                        format("'%s' configuration root doesn't exist", key), e
+                );
+            } else {
+                throw new IllegalArgumentException(
+                        format("'%s' configuration doesn't have the '%s' 
sub-configuration", join(path), key), e
                 );
             }
+        } catch (ConfigurationWrongPolymorphicTypeIdException e) {
+            throw new IllegalArgumentException(
+                    "Polymorphic configuration type is not correct: " + 
e.getMessage()
+            );
         }
     }
 
-    /** {@inheritDoc} */
     @Override
     public @Nullable String polymorphicTypeId(String fieldName) {
         ConfigValue typeId = hoconCfgObject.get(fieldName);
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconPrimitiveConfigurationSource.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconPrimitiveConfigurationSource.java
index 830b2567e7..4c50b42b27 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconPrimitiveConfigurationSource.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconPrimitiveConfigurationSource.java
@@ -58,7 +58,6 @@ class HoconPrimitiveConfigurationSource implements 
ConfigurationSource {
         this.hoconCfgValue = hoconCfgValue;
     }
 
-    /** {@inheritDoc} */
     @Override
     public <T> T unwrap(Class<T> clazz) {
         if (clazz.isArray()) {
@@ -68,12 +67,17 @@ class HoconPrimitiveConfigurationSource implements 
ConfigurationSource {
         return unwrapPrimitive(hoconCfgValue, clazz, path, -1);
     }
 
-    /** {@inheritDoc} */
     @Override
     public void descend(ConstructableTreeNode node) {
-        throw new IllegalArgumentException(
-                format("'%s' is expected to be a composite configuration node, 
not a single value", join(path))
-        );
+        String fieldName = node.injectedValueFieldName();
+
+        if (fieldName == null) {
+            throw new IllegalArgumentException(
+                    format("'%s' is expected to be a composite configuration 
node, not a single value", join(path))
+            );
+        }
+
+        node.construct(fieldName, this, false);
     }
 
     /**
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNode.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNode.java
index a82d45fa46..edd2c18343 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNode.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNode.java
@@ -18,6 +18,8 @@
 package org.apache.ignite.internal.configuration.tree;
 
 import java.util.NoSuchElementException;
+import org.apache.ignite.configuration.annotation.InjectedValue;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Interface for filling the configuration node.
@@ -48,4 +50,11 @@ public interface ConstructableTreeNode {
      * @return {@code true} if node became immutable, {@code false} if it has 
already been immutable before.
      */
     boolean makeImmutable();
+
+    /**
+     * Returns the name of the field annotated with {@link InjectedValue} or 
{@code null} if no such field exists.
+     */
+    default @Nullable String injectedValueFieldName() {
+        return null;
+    }
 }
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
index 76a396e6f7..4e90427b4f 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.internal.configuration.tree;
 
+import static org.apache.ignite.internal.util.IgniteUtils.newHashMap;
+
 import java.io.Serializable;
 import java.lang.reflect.Array;
 import java.lang.reflect.Field;
@@ -82,7 +84,7 @@ public class ConverterToMapVisitor implements 
ConfigurationVisitor<Object> {
         if (val instanceof Character || val instanceof UUID) {
             valObj = val.toString();
         } else if (val != null && val.getClass().isArray()) {
-            valObj = toListOfObjects(field, val);
+            valObj = toListOfObjects(val);
         } else if (val instanceof String) {
             valObj = maskIfNeeded(field, (String) val);
         }
@@ -92,7 +94,6 @@ public class ConverterToMapVisitor implements 
ConfigurationVisitor<Object> {
         return valObj;
     }
 
-    /** {@inheritDoc} */
     @Override
     public Object visitInnerNode(Field field, String key, InnerNode node) {
         if (skipEmptyValues && node == null) {
@@ -107,33 +108,74 @@ public class ConverterToMapVisitor implements 
ConfigurationVisitor<Object> {
 
         deque.pop();
 
-        addToParent(key, innerMap);
+        String injectedValueFieldName = node.injectedValueFieldName();
+
+        if (injectedValueFieldName != null) {
+            // If configuration contains an injected value, the rendered named 
list will be a map, where every injected value is represented
+            // as a separate key-value pair.
+            Object injectedValue = innerMap.get(injectedValueFieldName);
+
+            addToParent(key, injectedValue);
+
+            return injectedValue;
+        } else {
+            // Otherwise, the rendered named list will be a list of maps.
+            addToParent(key, innerMap);
 
-        return innerMap;
+            return innerMap;
+        }
     }
 
-    /** {@inheritDoc} */
     @Override
     public Object visitNamedListNode(Field field, String key, NamedListNode<?> 
node) {
-        if (skipEmptyValues && node.size() == 0) {
+        if (skipEmptyValues && node.isEmpty()) {
             return null;
         }
 
-        List<Object> list = new ArrayList<>(node.size());
+        Object renderedList;
+
+        boolean hasInjectedValues = !node.isEmpty() && 
getFirstNode(node).injectedValueFieldName() != null;
+
+        // See the comment inside "visitInnerNode" why named lists are 
rendered differently for injected values.
+        if (hasInjectedValues) {
+            Map<String, Object> map = newHashMap(node.size());
+
+            deque.push(map);
 
-        deque.push(list);
+            for (String subkey : node.namedListKeys()) {
+                InnerNode innerNode = node.getInnerNode(subkey);
 
-        for (String subkey : node.namedListKeys()) {
-            node.getInnerNode(subkey).accept(field, subkey, this);
+                innerNode.accept(field, subkey, this);
+            }
+
+            renderedList = map;
+        } else {
+            List<Object> list = new ArrayList<>(node.size());
+
+            deque.push(list);
+
+            for (String subkey : node.namedListKeys()) {
+                InnerNode innerNode = node.getInnerNode(subkey);
+
+                innerNode.accept(field, subkey, this);
 
-            ((Map<String, Object>) list.get(list.size() - 
1)).put(node.syntheticKeyName(), subkey);
+                ((Map<String, Object>) list.get(list.size() - 
1)).put(node.syntheticKeyName(), subkey);
+            }
+
+            renderedList = list;
         }
 
         deque.pop();
 
-        addToParent(key, list);
+        addToParent(key, renderedList);
+
+        return renderedList;
+    }
+
+    private static InnerNode getFirstNode(NamedListNode<?> namedListNode) {
+        String firstKey = namedListNode.namedListKeys().get(0);
 
-        return list;
+        return namedListNode.getInnerNode(firstKey);
     }
 
     /**
@@ -173,11 +215,10 @@ public class ConverterToMapVisitor implements 
ConfigurationVisitor<Object> {
     /**
      * Converts array into a list of objects. Boxes array elements if they are 
primitive values.
      *
-     * @param field Field of the array
      * @param val Array of primitives or array of {@link String}s
      * @return List of objects corresponding to the passed array.
      */
-    private List<?> toListOfObjects(Field field, Serializable val) {
+    private static List<?> toListOfObjects(Serializable val) {
         Stream<?> stream = IntStream.range(0, Array.getLength(val)).mapToObj(i 
-> Array.get(val, i));
 
         if (val.getClass().getComponentType() == char.class || 
val.getClass().getComponentType() == UUID.class) {
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
index 7ff2b2eae5..a0e1a28b96 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
@@ -56,6 +56,7 @@ import org.apache.ignite.configuration.annotation.ConfigValue;
 import org.apache.ignite.configuration.annotation.ConfigurationExtension;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.annotation.InjectedName;
+import org.apache.ignite.configuration.annotation.InjectedValue;
 import org.apache.ignite.configuration.annotation.InternalId;
 import org.apache.ignite.configuration.annotation.Name;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
@@ -443,7 +444,7 @@ public class ConfigurationUtil {
      * @return {@code true} if field represents primitive configuration.
      */
     public static boolean isValue(Field schemaField) {
-        return schemaField.isAnnotationPresent(Value.class);
+        return schemaField.isAnnotationPresent(Value.class) || 
schemaField.isAnnotationPresent(InjectedValue.class);
     }
 
     /**
@@ -498,6 +499,12 @@ public class ConfigurationUtil {
     public static boolean hasDefault(Field field) {
         assert isValue(field) : field;
 
+        InjectedValue injectedValue = field.getAnnotation(InjectedValue.class);
+
+        if (injectedValue != null) {
+            return injectedValue.hasDefault();
+        }
+
         return field.getAnnotation(Value.class).hasDefault();
     }
 
@@ -1129,6 +1136,20 @@ public class ConfigurationUtil {
         return schemaField.isAnnotationPresent(InjectedName.class);
     }
 
+    /**
+     * Returns {@code true} if the given schema field contains the {@link 
InjectedValue} annotation.
+     */
+    public static boolean isInjectedValue(Field schemaField) {
+        return schemaField.isAnnotationPresent(InjectedValue.class);
+    }
+
+    /**
+     * Returns {@code true} if the given schema field is read-only.
+     */
+    public static boolean isReadOnly(Field schemaField) {
+        return isPolymorphicId(schemaField) || isInjectedName(schemaField) || 
isInternalId(schemaField);
+    }
+
     /**
      * Checks whether configuration schema field contains {@link Name}.
      *
diff --git 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/InjectedValueConfigurationTest.java
 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/InjectedValueConfigurationTest.java
new file mode 100644
index 0000000000..3d929c3055
--- /dev/null
+++ 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/InjectedValueConfigurationTest.java
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.configuration;
+
+import static 
org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
+import static 
org.apache.ignite.internal.configuration.hocon.HoconConverter.hoconSource;
+import static 
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import com.typesafe.config.ConfigFactory;
+import com.typesafe.config.ConfigRenderOptions;
+import com.typesafe.config.ConfigValue;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.ignite.configuration.RootKey;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.InjectedName;
+import org.apache.ignite.configuration.annotation.InjectedValue;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.internal.configuration.hocon.HoconConverter;
+import 
org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
+import 
org.apache.ignite.internal.configuration.validation.TestConfigurationValidator;
+import org.apache.ignite.internal.manager.ComponentContext;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for schemas with {@link InjectedValue}s.
+ */
+public class InjectedValueConfigurationTest {
+    /** Root schema. */
+    @ConfigurationRoot(rootName = "rootInjectedValue", type = LOCAL)
+    public static class HoconInjectedValueRootConfigurationSchema {
+        @NamedConfigValue
+        public HoconInjectedValueConfigurationSchema nestedNamed;
+    }
+
+    /** Named list element schema. */
+    @Config
+    public static class HoconInjectedValueConfigurationSchema {
+        @InjectedName
+        public String someName;
+
+        @InjectedValue(hasDefault = true)
+        public String someValue = "default";
+    }
+
+    private ConfigurationRegistry registry;
+
+    @BeforeEach
+    void setUp() {
+        List<RootKey<?, ?>> roots = List.of(
+                HoconInjectedValueRootConfiguration.KEY
+        );
+
+        registry = new ConfigurationRegistry(
+                roots,
+                new TestConfigurationStorage(LOCAL),
+                new ConfigurationTreeGenerator(roots, List.of(), List.of()),
+                new TestConfigurationValidator()
+        );
+
+        assertThat(registry.startAsync(new ComponentContext()), 
willCompleteSuccessfully());
+    }
+
+    @AfterEach
+    void tearDown() {
+        assertThat(registry.stopAsync(new ComponentContext()), 
willCompleteSuccessfully());
+    }
+
+    @Nested
+    class HoconConverterTest {
+        @Test
+        void testEmpty() {
+            assertEquals("nestedNamed=[]", 
asHoconStr(List.of("rootInjectedValue")));
+        }
+
+        @Test
+        void testAssignValuesUsingObjectNotation() {
+            change("rootInjectedValue.nestedNamed = {foo: bar}");
+
+            assertEquals("nestedNamed{foo=bar}", 
asHoconStr(List.of("rootInjectedValue")));
+
+            change("rootInjectedValue.nestedNamed = {foo: bar, baz: quux}");
+
+            assertEquals("nestedNamed{baz=quux,foo=bar}", 
asHoconStr(List.of("rootInjectedValue")));
+
+            change("rootInjectedValue.nestedNamed = {baz: anotherQuux}");
+
+            assertEquals("nestedNamed{baz=anotherQuux,foo=bar}", 
asHoconStr(List.of("rootInjectedValue")));
+
+            change("rootInjectedValue.nestedNamed = {baz: null}");
+
+            assertEquals("nestedNamed{foo=bar}", 
asHoconStr(List.of("rootInjectedValue")));
+        }
+
+        @Test
+        void testAssignValuesUsingListNotation() {
+            change("rootInjectedValue.nestedNamed = [{foo=bar}]");
+
+            assertEquals("nestedNamed{foo=bar}", 
asHoconStr(List.of("rootInjectedValue")));
+
+            change("rootInjectedValue.nestedNamed = [{foo=bar},{baz=quux}]");
+
+            assertEquals("nestedNamed{baz=quux,foo=bar}", 
asHoconStr(List.of("rootInjectedValue")));
+
+            change("rootInjectedValue.nestedNamed = [{baz=anotherQuux}]");
+
+            assertEquals("nestedNamed{baz=anotherQuux,foo=bar}", 
asHoconStr(List.of("rootInjectedValue")));
+
+            // Removing a value does not work in this notation.
+        }
+    }
+
+    @Nested
+    class JavaApiTest {
+        @Test
+        void testEmpty() {
+            HoconInjectedValueRootConfiguration cfg = 
registry.getConfiguration(HoconInjectedValueRootConfiguration.KEY);
+
+            assertThat(cfg.nestedNamed().value().size(), is(0));
+        }
+
+        @Test
+        void testDefaults() {
+            HoconInjectedValueRootConfiguration cfg = 
registry.getConfiguration(HoconInjectedValueRootConfiguration.KEY);
+
+            CompletableFuture<Void> changeFuture = cfg.change(rootChange -> 
rootChange
+                    .changeNestedNamed(nestedChange -> nestedChange
+                            .create("foo", valueChange -> {})));
+
+            assertThat(changeFuture, willCompleteSuccessfully());
+
+            assertThat(cfg.value().nestedNamed().get("foo").someValue(), 
is("default"));
+            assertThat(cfg.nestedNamed().value().get("foo").someValue(), 
is("default"));
+            assertThat(cfg.nestedNamed().get("foo").value().someValue(), 
is("default"));
+            assertThat(cfg.nestedNamed().get("foo").someValue().value(), 
is("default"));
+        }
+
+        @Test
+        void testAssignValues() {
+            HoconInjectedValueRootConfiguration cfg = 
registry.getConfiguration(HoconInjectedValueRootConfiguration.KEY);
+
+            CompletableFuture<Void> changeFuture = cfg.change(rootChange -> 
rootChange
+                    .changeNestedNamed(nestedChange -> nestedChange
+                            .create("foo", valueChange -> 
valueChange.changeSomeValue("bar"))));
+
+            assertThat(changeFuture, willCompleteSuccessfully());
+
+            assertThat(cfg.value().nestedNamed().get("foo").someValue(), 
is("bar"));
+            assertThat(cfg.nestedNamed().value().get("foo").someValue(), 
is("bar"));
+            assertThat(cfg.nestedNamed().get("foo").value().someValue(), 
is("bar"));
+            assertThat(cfg.nestedNamed().get("foo").someValue().value(), 
is("bar"));
+        }
+    }
+
+    private void change(String hocon) {
+        assertThat(
+                
registry.change(hoconSource(ConfigFactory.parseString(hocon).root())),
+                willCompleteSuccessfully()
+        );
+    }
+
+    private String asHoconStr(List<String> basePath, String... path) {
+        List<String> fullPath = Stream.concat(basePath.stream(), 
Arrays.stream(path)).collect(Collectors.toList());
+
+        ConfigValue hoconCfg = HoconConverter.represent(registry.superRoot(), 
fullPath);
+
+        return hoconCfg.render(ConfigRenderOptions.concise().setJson(false));
+    }
+}
diff --git 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
index f403cb9312..7909d9cd43 100644
--- 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
+++ 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
@@ -731,7 +731,7 @@ public class HoconConverterTest {
         // Let's check that the NamedConfigValue#syntheticKeyName key will not 
work.
         assertThrowsIllegalArgException(
                 () -> change("rootInjectedName.nestedNamed = [{superName = 
foo}]"),
-                "'rootInjectedName.nestedNamed[0].someName' configuration 
value is mandatory and must be a String"
+                "'rootInjectedName.nestedNamed' configuration doesn't have the 
'superName' sub-configuration"
         );
     }
 
diff --git 
a/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItDistributionZonesFiltersTest.java
 
b/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItDistributionZonesFiltersTest.java
index 8bfd18470f..14786209b3 100644
--- 
a/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItDistributionZonesFiltersTest.java
+++ 
b/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/distributionzones/ItDistributionZonesFiltersTest.java
@@ -63,7 +63,7 @@ public class ItDistributionZonesFiltersTest extends 
ClusterPerTestIntegrationTes
     private static final String TABLE_NAME = "table1";
 
     @Language("HOCON")
-    private static final String NODE_ATTRIBUTES = "{region.attribute = US, 
storage.attribute = SSD}";
+    private static final String NODE_ATTRIBUTES = "{region = US, storage = 
SSD}";
 
     private static final String STORAGE_PROFILES = String.format("'%s, %s'", 
DEFAULT_ROCKSDB_PROFILE_NAME, DEFAULT_AIPERSIST_PROFILE_NAME);
 
@@ -115,7 +115,7 @@ public class ItDistributionZonesFiltersTest extends 
ClusterPerTestIntegrationTes
         String filter = "$[?(@.region == \"US\" && @.storage == \"SSD\")]";
 
         // This node do not pass the filter
-        @Language("HOCON") String firstNodeAttributes = 
"{region:{attribute:\"EU\"},storage:{attribute:\"SSD\"}}";
+        @Language("HOCON") String firstNodeAttributes = "{region: EU, storage: 
SSD}";
 
         Ignite node = startNode(1, createStartConfig(firstNodeAttributes, 
STORAGE_PROFILES_CONFIGS));
 
@@ -123,10 +123,10 @@ public class ItDistributionZonesFiltersTest extends 
ClusterPerTestIntegrationTes
 
         node.sql().execute(null, createTableSql());
 
-        MetaStorageManager metaStorageManager = (MetaStorageManager) 
IgniteTestUtils
+        MetaStorageManager metaStorageManager = IgniteTestUtils
                 .getFieldValue(node, IgniteImpl.class, "metaStorageMgr");
 
-        TableManager tableManager = (TableManager) 
IgniteTestUtils.getFieldValue(node, IgniteImpl.class, "distributedTblMgr");
+        TableManager tableManager = IgniteTestUtils.getFieldValue(node, 
IgniteImpl.class, "distributedTblMgr");
 
         TableViewInternal table = (TableViewInternal) 
tableManager.table(TABLE_NAME);
 
@@ -140,7 +140,7 @@ public class ItDistributionZonesFiltersTest extends 
ClusterPerTestIntegrationTes
                 TIMEOUT_MILLIS
         );
 
-        @Language("HOCON") String secondNodeAttributes = 
"{region:{attribute:\"US\"},storage:{attribute:\"SSD\"}}";
+        @Language("HOCON") String secondNodeAttributes = "{region: US, 
storage: SSD}";
 
         // This node pass the filter but storage profiles of a node do not 
match zone's storage profiles.
         // TODO: https://issues.apache.org/jira/browse/IGNITE-21387 recovery 
of this node is failing,
@@ -210,7 +210,7 @@ public class ItDistributionZonesFiltersTest extends 
ClusterPerTestIntegrationTes
                 TIMEOUT_MILLIS
         );
 
-        @Language("HOCON") String firstNodeAttributes = 
"{region:{attribute:\"US\"},storage:{attribute:\"SSD\"}}";
+        @Language("HOCON") String firstNodeAttributes = "{region: US, storage: 
SSD}";
 
         // This node pass the filter
         startNode(1, createStartConfig(firstNodeAttributes, 
STORAGE_PROFILES_CONFIGS));
@@ -265,7 +265,7 @@ public class ItDistributionZonesFiltersTest extends 
ClusterPerTestIntegrationTes
                 TIMEOUT_MILLIS
         );
 
-        @Language("HOCON") String firstNodeAttributes = 
"{region:{attribute:\"US\"},storage:{attribute:\"SSD\"}}";
+        @Language("HOCON") String firstNodeAttributes = "{region: US, storage: 
SSD}";
 
         // This node pass the filter
         startNode(1, createStartConfig(firstNodeAttributes, 
STORAGE_PROFILES_CONFIGS));
@@ -306,7 +306,7 @@ public class ItDistributionZonesFiltersTest extends 
ClusterPerTestIntegrationTes
         Ignite node0 = unwrapIgniteImpl(node(0));
 
         // This node passes the filter
-        @Language("HOCON") String firstNodeAttributes = 
"{region:{attribute:\"EU\"},storage:{attribute:\"HDD\"}}";
+        @Language("HOCON") String firstNodeAttributes = "{region: EU, storage: 
HDD}";
 
         Ignite node1 = startNode(1, createStartConfig(firstNodeAttributes, 
STORAGE_PROFILES_CONFIGS));
 
@@ -324,7 +324,7 @@ public class ItDistributionZonesFiltersTest extends 
ClusterPerTestIntegrationTes
 
         node1.sql().execute(null, createTableSql());
 
-        TableManager tableManager = (TableManager) 
IgniteTestUtils.getFieldValue(node0, IgniteImpl.class, "distributedTblMgr");
+        TableManager tableManager = IgniteTestUtils.getFieldValue(node0, 
IgniteImpl.class, "distributedTblMgr");
 
         TableViewInternal table = (TableViewInternal) 
tableManager.table(TABLE_NAME);
 
@@ -350,7 +350,7 @@ public class ItDistributionZonesFiltersTest extends 
ClusterPerTestIntegrationTes
         IgniteImpl node0 = unwrapIgniteImpl(node(0));
 
         // This node passes the filter
-        @Language("HOCON") String firstNodeAttributes = 
"{region:{attribute:\"EU\"},storage:{attribute:\"HDD\"}}";
+        @Language("HOCON") String firstNodeAttributes = "{region: EU, storage: 
HDD}";
 
         startNode(1, createStartConfig(firstNodeAttributes, 
STORAGE_PROFILES_CONFIGS));
 
diff --git 
a/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/rebalance/ItRebalanceTriggersRecoveryTest.java
 
b/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/rebalance/ItRebalanceTriggersRecoveryTest.java
index ac954febcd..6572931658 100644
--- 
a/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/rebalance/ItRebalanceTriggersRecoveryTest.java
+++ 
b/modules/distribution-zones/src/integrationTest/java/org/apache/ignite/internal/rebalance/ItRebalanceTriggersRecoveryTest.java
@@ -62,7 +62,7 @@ public class ItRebalanceTriggersRecoveryTest extends 
ClusterPerTestIntegrationTe
             + "  },\n"
             + "  clientConnector: { port:{} },\n"
             + "  nodeAttributes: {\n"
-            + "    nodeAttributes: {region: {attribute: \"US\"}, zone: 
{attribute: \"global\"}}\n"
+            + "    nodeAttributes: {region: US, zone: global}\n"
             + "  },\n"
             + "  rest.port: {},\n"
             + "  failureHandler.dumpThreadsOnFailure: false\n"
@@ -77,7 +77,7 @@ public class ItRebalanceTriggersRecoveryTest extends 
ClusterPerTestIntegrationTe
             + "  },\n"
             + "  clientConnector: { port:{} },\n"
             + "  nodeAttributes: {\n"
-            + "    nodeAttributes: {zone: {attribute: \"global\"}}\n"
+            + "    nodeAttributes: {zone: global}\n"
             + "  },\n"
             + "  rest.port: {},\n"
             + "  failureHandler.dumpThreadsOnFailure: false\n"
diff --git 
a/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/BaseDistributionZoneManagerTest.java
 
b/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/BaseDistributionZoneManagerTest.java
index 251e3bbd60..26909c6b2a 100644
--- 
a/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/BaseDistributionZoneManagerTest.java
+++ 
b/modules/distribution-zones/src/test/java/org/apache/ignite/internal/distributionzones/BaseDistributionZoneManagerTest.java
@@ -89,9 +89,7 @@ public abstract class BaseDistributionZoneManagerTest extends 
BaseIgniteAbstract
 
     private final List<IgniteComponent> components = new ArrayList<>();
 
-    @InjectConfiguration("mock.properties = {"
-            + PARTITION_DISTRIBUTION_RESET_TIMEOUT + ".propertyValue = \"" + 
IMMEDIATE_TIMER_VALUE + "\", "
-            + "}")
+    @InjectConfiguration("mock.properties." + 
PARTITION_DISTRIBUTION_RESET_TIMEOUT + " = \"" + IMMEDIATE_TIMER_VALUE + "\"")
     SystemDistributedConfiguration systemDistributedConfiguration;
 
     @BeforeEach
diff --git a/modules/distribution-zones/tech-notes/filters.md 
b/modules/distribution-zones/tech-notes/filters.md
index 0cad552679..22269fdf69 100644
--- a/modules/distribution-zones/tech-notes/filters.md
+++ b/modules/distribution-zones/tech-notes/filters.md
@@ -16,8 +16,8 @@ In the `ignite-config.conf` it looks like this:
 
 ```
 nodeAttributes.nodeAttributes {
-      region.attribute = "US"
-      storage.attribute = "SSD"
+      region = "US"
+      storage = "SSD"
 }
 ```
 
@@ -27,12 +27,8 @@ or like this in a `ignite-config.json`:
 {
    "nodeAttributes":{
       "nodeAttributes":{
-         "region":{
-            "attribute":"US"
-         },
-         "storage":{
-            "attribute":"SSD"
-         }
+         "region": "US",
+         "storage": "SSD"
       }
    }
 }
@@ -65,6 +61,3 @@ To check all capabilities of JSONPath, see 
https://github.com/json-path/JsonPath
 
 Note that as a default value for filter '$..*' filter is used, meaning that 
all nodes match the filter.
 Also it is important, that if there are no specified attributes for a node, it 
means that a node match only the default filter. 
-
-
-
diff --git 
a/modules/metastorage/src/integrationTest/java/org/apache/ignite/internal/metastorage/TestMetasStorageUtils.java
 
b/modules/metastorage/src/integrationTest/java/org/apache/ignite/internal/metastorage/TestMetasStorageUtils.java
index 67deee8707..40a4482d30 100644
--- 
a/modules/metastorage/src/integrationTest/java/org/apache/ignite/internal/metastorage/TestMetasStorageUtils.java
+++ 
b/modules/metastorage/src/integrationTest/java/org/apache/ignite/internal/metastorage/TestMetasStorageUtils.java
@@ -81,8 +81,8 @@ public class TestMetasStorageUtils {
     public static String createClusterConfigWithCompactionProperties(long 
interval, long dataAvailabilityTime) {
         return String.format(
                 "ignite.system.properties: {"
-                        + "%s.propertyValue= \"%s\", "
-                        + "%s.propertyValue= \"%s\""
+                        + "%s = \"%s\", "
+                        + "%s = \"%s\""
                         + "}",
                 INTERVAL_SYSTEM_PROPERTY_NAME, interval, 
DATA_AVAILABILITY_TIME_SYSTEM_PROPERTY_NAME, dataAvailabilityTime
         );
diff --git 
a/modules/metastorage/src/test/java/org/apache/ignite/internal/metastorage/impl/MetaStorageCompactionTriggerConfigurationTest.java
 
b/modules/metastorage/src/test/java/org/apache/ignite/internal/metastorage/impl/MetaStorageCompactionTriggerConfigurationTest.java
index 296fed8b93..17364d6ee4 100644
--- 
a/modules/metastorage/src/test/java/org/apache/ignite/internal/metastorage/impl/MetaStorageCompactionTriggerConfigurationTest.java
+++ 
b/modules/metastorage/src/test/java/org/apache/ignite/internal/metastorage/impl/MetaStorageCompactionTriggerConfigurationTest.java
@@ -52,8 +52,8 @@ public class MetaStorageCompactionTriggerConfigurationTest 
extends BaseIgniteAbs
     @Test
     void testValidSystemPropertiesOnStart(
             @InjectConfiguration("mock.properties = {"
-                    + INTERVAL_SYSTEM_PROPERTY_NAME + ".propertyValue = 
\"100\", "
-                    + DATA_AVAILABILITY_TIME_SYSTEM_PROPERTY_NAME + 
".propertyValue = \"500\""
+                    + INTERVAL_SYSTEM_PROPERTY_NAME + " = \"100\", "
+                    + DATA_AVAILABILITY_TIME_SYSTEM_PROPERTY_NAME + " = 
\"500\""
                     + "}")
             SystemDistributedConfiguration systemConfig
     ) {
diff --git 
a/modules/partition-replicator/src/integrationTest/java/org/apache/ignite/internal/partition/replicator/ItReplicaLifecycleTest.java
 
b/modules/partition-replicator/src/integrationTest/java/org/apache/ignite/internal/partition/replicator/ItReplicaLifecycleTest.java
index 749194de65..6280a5e0f6 100644
--- 
a/modules/partition-replicator/src/integrationTest/java/org/apache/ignite/internal/partition/replicator/ItReplicaLifecycleTest.java
+++ 
b/modules/partition-replicator/src/integrationTest/java/org/apache/ignite/internal/partition/replicator/ItReplicaLifecycleTest.java
@@ -256,13 +256,13 @@ public class ItReplicaLifecycleTest extends 
BaseIgniteAbstractTest {
     @InjectConfiguration
     private static SystemLocalConfiguration systemConfiguration;
 
-    @InjectConfiguration("mock.nodeAttributes: {region.attribute = US, 
storage.attribute = SSD}")
+    @InjectConfiguration("mock.nodeAttributes: {region = US, storage = SSD}")
     private static NodeAttributesConfiguration nodeAttributes1;
 
-    @InjectConfiguration("mock.nodeAttributes: {region.attribute = EU, 
storage.attribute = SSD}")
+    @InjectConfiguration("mock.nodeAttributes: {region = EU, storage = SSD}")
     private static NodeAttributesConfiguration nodeAttributes2;
 
-    @InjectConfiguration("mock.nodeAttributes: {region.attribute = UK, 
storage.attribute = SSD}")
+    @InjectConfiguration("mock.nodeAttributes: {region = UK, storage = SSD}")
     private static NodeAttributesConfiguration nodeAttributes3;
 
     @InjectConfiguration
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
index e9efcc2d8c..3e86b870d6 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
@@ -1028,8 +1028,8 @@ public class ItIgniteNodeRestartTest extends 
BaseIgniteRestartTest {
         stopNode(0);
 
         String newAttributesCfg = "{\n"
-                + "      region.attribute = \"US\"\n"
-                + "      storage.attribute = \"SSD\"\n"
+                + "      region = US\n"
+                + "      storage = SSD\n"
                 + "}";
 
         Map<String, String> newAttributesMap = Map.of("region", "US", 
"storage", "SSD");
diff --git 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItReplicaStateManagerTest.java
 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItReplicaStateManagerTest.java
index 6f2cd40ea4..615a03b0bc 100644
--- 
a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItReplicaStateManagerTest.java
+++ 
b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItReplicaStateManagerTest.java
@@ -52,9 +52,9 @@ import org.junit.jupiter.api.Test;
  */
 public class ItReplicaStateManagerTest extends BaseIgniteRestartTest {
     private static final String[] ATTRIBUTES = {
-            "{region:{attribute:\"REG0\"}}",
-            "{region:{attribute:\"REG1\"}}",
-            "{region:{attribute:\"REG2\"}}"
+            "{ region: REG0 }",
+            "{ region: REG1 }",
+            "{ region: REG2 }"
     };
 
     private static final String ZONE_NAME = "TEST_ZONE";
diff --git 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItUnstableTopologyTest.java
 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItUnstableTopologyTest.java
index 6c703266b7..3c716423b9 100644
--- 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItUnstableTopologyTest.java
+++ 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItUnstableTopologyTest.java
@@ -37,7 +37,7 @@ public class ItUnstableTopologyTest extends 
BaseSqlIntegrationTest {
             + "  },\n"
             + "  clientConnector: { port:{} },\n"
             + "  nodeAttributes: {\n"
-            + "    nodeAttributes: {role: {attribute: \"data\"}}\n"
+            + "    nodeAttributes: { role: data }\n"
             + "  },\n"
             + "  rest.port: {},\n"
             + "  failureHandler.dumpThreadsOnFailure: false\n"
diff --git 
a/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/distributed/disaster/ItHighAvailablePartitionsRecoveryByFilterUpdateTest.java
 
b/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/distributed/disaster/ItHighAvailablePartitionsRecoveryByFilterUpdateTest.java
index 08dc440b6a..7456704f5f 100644
--- 
a/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/distributed/disaster/ItHighAvailablePartitionsRecoveryByFilterUpdateTest.java
+++ 
b/modules/table/src/integrationTest/java/org/apache/ignite/internal/table/distributed/disaster/ItHighAvailablePartitionsRecoveryByFilterUpdateTest.java
@@ -24,11 +24,11 @@ import org.junit.jupiter.api.Test;
 
 /** Test suite for the cases with a recovery of the group replication factor 
after reset by zone filter update. */
 public class ItHighAvailablePartitionsRecoveryByFilterUpdateTest extends 
AbstractHighAvailablePartitionsRecoveryTest {
-    private static final String GLOBAL_EU_NODES_CONFIG = 
nodeConfig("{region.attribute = EU, zone.attribute = global}");
+    private static final String GLOBAL_EU_NODES_CONFIG = nodeConfig("{region = 
EU, zone = global}");
 
-    private static final String EU_ONLY_NODES_CONFIG = 
nodeConfig("{region.attribute = EU}");
+    private static final String EU_ONLY_NODES_CONFIG = nodeConfig("{region = 
EU}");
 
-    private static final String GLOBAL_NODES_CONFIG = 
nodeConfig("{zone.attribute = global}");
+    private static final String GLOBAL_NODES_CONFIG = nodeConfig("{zone = 
global}");
 
     @Override
     protected int initialNodes() {


Reply via email to