IGNITE-14924 HOCON configuration source and representation implemented. (#228)

diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
new file mode 100644
index 0000000..e0a8fe6
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
@@ -0,0 +1,567 @@
+/*
+ * 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.hocon;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import com.typesafe.config.ConfigFactory;
+import com.typesafe.config.ConfigRenderOptions;
+import com.typesafe.config.ConfigValue;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.ConfigurationType;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.internal.configuration.ConfigurationRegistry;
+import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.ignite.internal.configuration.hocon.HoconConverter.hoconSource;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for the {@link HoconConverter}.
+ */
+public class HoconConverterTest {
+    /** */
+    @ConfigurationRoot(rootName = "root", type = ConfigurationType.LOCAL)
+    public static class HoconRootConfigurationSchema {
+        /** */
+        @NamedConfigValue(syntheticKeyName = "a")
+        public HoconArraysConfigurationSchema arraysList;
+
+        /** */
+        @NamedConfigValue(syntheticKeyName = "p")
+        public HoconPrimitivesConfigurationSchema primitivesList;
+    }
+
+    /**
+     * Configuration schema for testing the support of arrays of primitives.
+     */
+    @Config
+    public static class HoconArraysConfigurationSchema {
+        /** */
+        @Value(hasDefault = true)
+        public boolean[] booleans = {false};
+
+        /** */
+        @Value(hasDefault = true)
+        public byte[] bytes = {0};
+
+        /** */
+        @Value(hasDefault = true)
+        public short[] shorts = {0};
+
+        /** */
+        @Value(hasDefault = true)
+        public int[] ints = {0};
+
+        /** */
+        @Value(hasDefault = true)
+        public long[] longs = {0L};
+
+        /** */
+        @Value(hasDefault = true)
+        public char[] chars = {0};
+
+        /** */
+        @Value(hasDefault = true)
+        public float[] floats = {0};
+
+        /** */
+        @Value(hasDefault = true)
+        public double[] doubles = {0};
+
+        /** */
+        @Value(hasDefault = true)
+        public String[] strings = {""};
+    }
+
+    /**
+     * Configuration schema for testing the support of primitives.
+     */
+    @Config
+    public static class HoconPrimitivesConfigurationSchema {
+        /** */
+        @Value(hasDefault = true)
+        public boolean booleanVal = false;
+
+        /** */
+        @Value(hasDefault = true)
+        public byte byteVal = 0;
+
+        /** */
+        @Value(hasDefault = true)
+        public short shortVal = 0;
+
+        /** */
+        @Value(hasDefault = true)
+        public int intVal = 0;
+
+        /** */
+        @Value(hasDefault = true)
+        public long longVal = 0L;
+
+        /** */
+        @Value(hasDefault = true)
+        public char charVal = 0;
+
+        /** */
+        @Value(hasDefault = true)
+        public float floatVal = 0;
+
+        /** */
+        @Value(hasDefault = true)
+        public double doubleVal = 0;
+
+        /** */
+        @Value(hasDefault = true)
+        public String stringVal = "";
+    }
+
+    /** */
+    private static ConfigurationRegistry registry;
+
+    /** */
+    private static HoconRootConfiguration configuration;
+
+    /** */
+    @BeforeAll
+    public static void beforeAll() {
+        registry = new ConfigurationRegistry(
+            Collections.singletonList(HoconRootConfiguration.KEY),
+            Collections.emptyMap(),
+            Collections.singletonList(new TestConfigurationStorage())
+        );
+
+        configuration = registry.getConfiguration(HoconRootConfiguration.KEY);
+    }
+
+    /** */
+    @AfterAll
+    public static void after() {
+        registry.stop();
+
+        registry = null;
+
+        configuration = null;
+    }
+
+    /** */
+    @BeforeEach
+    public void before() throws Exception {
+        configuration.change(cfg -> cfg
+            .changePrimitivesList(list -> list.namedListKeys().forEach(list::delete))
+            .changeArraysList(list -> list.namedListKeys().forEach(list::delete))
+        ).get(1, SECONDS);
+    }
+
+    /** */
+    @Test
+    public void toHoconBasic() {
+        assertEquals("root{arraysList=[],primitivesList=[]}", asHoconStr(List.of()));
+
+        assertEquals("arraysList=[],primitivesList=[]", asHoconStr(List.of("root")));
+
+        assertEquals("[]", asHoconStr(List.of("root", "arraysList")));
+
+        assertThrowsIllegalArgException(
+            () -> HoconConverter.represent(registry, List.of("doot")),
+            "Configuration 'doot' is not found"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> HoconConverter.represent(registry, List.of("root", "x")),
+            "Configuration 'root.x' is not found"
+        );
+
+        assertEquals("null", asHoconStr(List.of("root", "primitivesList", "foo")));
+    }
+
+    /**
+     * Tests that the {@code HoconConverter} supports serialization of Strings and primitives.
+     */
+    @Test
+    public void testHoconPrimitivesSerialization() throws Exception {
+        configuration.change(cfg -> cfg
+            .changePrimitivesList(primitivesList -> primitivesList
+                .create("name", primitives -> {})
+            )
+        ).get(1, SECONDS);
+
+        var basePath = List.of("root", "primitivesList", "name");
+
+        assertEquals(
+            "booleanVal=false,byteVal=0,charVal=\"\\u0000\",doubleVal=0.0,floatVal=0,intVal=0,longVal=0,shortVal=0,stringVal=\"\"",
+            asHoconStr(basePath)
+        );
+
+        assertEquals("false", asHoconStr(basePath, "booleanVal"));
+        assertEquals("0", asHoconStr(basePath, "byteVal"));
+        assertEquals("0", asHoconStr(basePath, "shortVal"));
+        assertEquals("0", asHoconStr(basePath, "intVal"));
+        assertEquals("0", asHoconStr(basePath, "longVal"));
+        assertEquals("\"\\u0000\"", asHoconStr(basePath, "charVal"));
+        assertEquals("0", asHoconStr(basePath, "floatVal"));
+        assertEquals("0.0", asHoconStr(basePath, "doubleVal"));
+        assertEquals("\"\"", asHoconStr(basePath, "stringVal"));
+    }
+
+    /**
+     * Tests that the {@code HoconConverter} supports serialization of arrays of Strings and primitives.
+     */
+    @Test
+    public void testHoconArraysSerialization() throws Exception {
+        configuration.change(cfg -> cfg
+            .changeArraysList(arraysList -> arraysList
+                .create("name", arrays -> {})
+            )
+        ).get(1, SECONDS);
+
+        var basePath = List.of("root", "arraysList", "name");
+
+        assertEquals(
+            "booleans=[false],bytes=[0],chars=[\"\\u0000\"],doubles=[0.0],floats=[0],ints=[0],longs=[0],shorts=[0],strings=[\"\"]",
+            asHoconStr(basePath)
+        );
+
+        assertEquals("[false]", asHoconStr(basePath, "booleans"));
+        assertEquals("[0]", asHoconStr(basePath, "bytes"));
+        assertEquals("[0]", asHoconStr(basePath, "shorts"));
+        assertEquals("[0]", asHoconStr(basePath, "ints"));
+        assertEquals("[0]", asHoconStr(basePath, "longs"));
+        assertEquals("[\"\\u0000\"]", asHoconStr(basePath, "chars"));
+        assertEquals("[0]", asHoconStr(basePath, "floats"));
+        assertEquals("[0.0]", asHoconStr(basePath, "doubles"));
+        assertEquals("[\"\"]", asHoconStr(basePath, "strings"));
+    }
+
+    /**
+     * Retrieves the HOCON configuration located at the given path.
+     */
+    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, fullPath);
+
+        return hoconCfg.render(ConfigRenderOptions.concise().setJson(false));
+    }
+
+    /** */
+    @Test
+    public void fromHoconBasic() {
+        // Wrong names:
+        assertThrowsIllegalArgException(
+            () -> change("doot : {}"),
+            "'doot' configuration root doesn't exist"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.foo : {}"),
+            "'root' configuration doesn't have the 'foo' sub-configuration"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList.name.x = 1"),
+            "'root.arraysList.name' configuration doesn't have the 'x' sub-configuration"
+        );
+
+        // Wrong node types:
+        assertThrowsIllegalArgException(
+            () -> change("root = foo"),
+            "'root' is expected to be a composite configuration node, not a single value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList = foo"),
+            "'root.arraysList' is expected to be a composite configuration node, not a single value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList.name = foo"),
+            "'root.arraysList.name' is expected to be a composite configuration node, not a single value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList.name.ints = {}"),
+            "'int[]' is expected as a type for the 'root.arraysList.name.ints' configuration value"
+        );
+
+        // Wrong ordered named list syntax:
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList = [1]"),
+            "'root.arraysList[0]' is expected to be a composite configuration node, not a single value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList = [{}]"),
+            "'root.arraysList[0].a' configuration value is mandatory and must be a String"
+        );
+    }
+
+    /**
+     * Tests that the {@code HoconConverter} supports deserialization of Strings and primitives.
+     */
+    @Test
+    public void testHoconPrimitivesDeserialization() throws Throwable {
+        change("root.primitivesList = [{p = name}]");
+
+        HoconPrimitivesConfiguration primitives = configuration.primitivesList().get("name");
+        assertNotNull(primitives);
+
+        change("root.primitivesList.name.booleanVal = true");
+        assertThat(primitives.booleanVal().value(), is(true));
+
+        change("root.primitivesList.name.byteVal = 123");
+        assertThat(primitives.byteVal().value(), is((byte)123));
+
+        change("root.primitivesList.name.shortVal = 12345");
+        assertThat(primitives.shortVal().value(), is((short)12345));
+
+        change("root.primitivesList.name.intVal = 12345");
+        assertThat(primitives.intVal().value(), is(12345));
+
+        change("root.primitivesList.name.longVal = 12345678900");
+        assertThat(primitives.longVal().value(), is(12345678900L));
+
+        change("root.primitivesList.name.charVal = p");
+        assertThat(primitives.charVal().value(), is('p'));
+
+        change("root.primitivesList.name.floatVal = 2.5");
+        assertThat(primitives.floatVal().value(), is(2.5f));
+
+        change("root.primitivesList.name.doubleVal = 2.5");
+        assertThat(primitives.doubleVal().value(), is(2.5d));
+
+        change("root.primitivesList.name.stringVal = foo");
+        assertThat(primitives.stringVal().value(), is("foo"));
+    }
+
+    /**
+     * Tests deserialization errors that can happen during the deserialization of primitives.
+     */
+    @Test
+    public void testInvalidHoconPrimitivesDeserialization() {
+        assertThrowsIllegalArgException(
+            () -> change("root.primitivesList.name.booleanVal = \"true\""),
+            "'boolean' is expected as a type for the 'root.primitivesList.name.booleanVal' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.primitivesList.name.byteVal = 290"),
+            "Value '290' of 'root.primitivesList.name.byteVal' is out of its declared bounds"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.primitivesList.name.byteVal = false"),
+            "'byte' is expected as a type for the 'root.primitivesList.name.byteVal' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.primitivesList.name.shortVal = 12345678900"),
+            "Value '12345678900' of 'root.primitivesList.name.shortVal' is out of its declared bounds"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.primitivesList.name.shortVal = false"),
+            "'short' is expected as a type for the 'root.primitivesList.name.shortVal' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.primitivesList.name.intVal = 12345678900"),
+            "Value '12345678900' of 'root.primitivesList.name.intVal' is out of its declared bounds"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.primitivesList.name.intVal = false"),
+            "'int' is expected as a type for the 'root.primitivesList.name.intVal' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.primitivesList.name.longVal = false"),
+            "'long' is expected as a type for the 'root.primitivesList.name.longVal' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.primitivesList.name.charVal = 10"),
+            "'char' is expected as a type for the 'root.primitivesList.name.charVal' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.primitivesList.name.floatVal = false"),
+            "'float' is expected as a type for the 'root.primitivesList.name.floatVal' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.primitivesList.name.doubleVal = []"),
+            "'double' is expected as a type for the 'root.primitivesList.name.doubleVal' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.primitivesList.name.stringVal = 10"),
+            "'String' is expected as a type for the 'root.primitivesList.name.stringVal' configuration value"
+        );
+    }
+
+    /**
+     * Tests that the {@code HoconConverter} supports deserialization of arrays of Strings and primitives.
+     */
+    @Test
+    public void testHoconArraysDeserialization() throws Throwable {
+        change("root.arraysList = [{a = name}]");
+
+        HoconArraysConfiguration arrays = configuration.arraysList().get("name");
+        assertNotNull(arrays);
+
+        change("root.arraysList.name.booleans = [true]");
+        assertThat(arrays.booleans().value(), is(new boolean[] {true}));
+
+        change("root.arraysList.name.bytes = [123]");
+        assertThat(arrays.bytes().value(), is(new byte[] {123}));
+
+        change("root.arraysList.name.shorts = [123]");
+        assertThat(arrays.shorts().value(), is(new short[] {123}));
+
+        change("root.arraysList.name.ints = [12345]");
+        assertThat(arrays.ints().value(), is(new int[] {12345}));
+
+        change("root.arraysList.name.longs = [12345678900]");
+        assertThat(arrays.longs().value(), is(new long[] {12345678900L}));
+
+        change("root.arraysList.name.chars = [p]");
+        assertThat(arrays.chars().value(), is(new char[] {'p'}));
+
+        change("root.arraysList.name.floats = [2.5]");
+        assertThat(arrays.floats().value(), is(new float[] {2.5f}));
+
+        change("root.arraysList.name.doubles = [2.5]");
+        assertThat(arrays.doubles().value(), is(new double[] {2.5d}));
+
+        change("root.arraysList.name.strings = [foo]");
+        assertThat(arrays.strings().value(), is(new String[] {"foo"}));
+    }
+
+    /**
+     * Tests deserialization errors that can happen during the deserialization of arrays of primitives.
+     */
+    @Test
+    public void testInvalidHoconArraysDeserialization() {
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList.name.booleans = true"),
+            "'boolean[]' is expected as a type for the 'root.arraysList.name.booleans' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList.name.booleans = [{}]"),
+            "'boolean' is expected as a type for the 'root.arraysList.name.booleans[0]' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList.name.bytes = [123, 290]"),
+            "Value '290' of 'root.arraysList.name.bytes[1]' is out of its declared bounds"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList.name.bytes = false"),
+            "'byte[]' is expected as a type for the 'root.arraysList.name.bytes' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList.name.shorts = [12345678900]"),
+            "Value '12345678900' of 'root.arraysList.name.shorts[0]' is out of its declared bounds"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList.name.shorts = [123, false]"),
+            "'short' is expected as a type for the 'root.arraysList.name.shorts[1]' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList.name.ints = [5, 12345678900]"),
+            "Value '12345678900' of 'root.arraysList.name.ints[1]' is out of its declared bounds"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList.name.ints = false"),
+            "'int[]' is expected as a type for the 'root.arraysList.name.ints' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList.name.longs = [foo]"),
+            "'long' is expected as a type for the 'root.arraysList.name.longs[0]' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList.name.chars = 10"),
+            "'char[]' is expected as a type for the 'root.arraysList.name.chars' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList.name.chars = [abc]"),
+            "'char' is expected as a type for the 'root.arraysList.name.chars[0]' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList.name.floats = [1.2, foo]"),
+            "'float' is expected as a type for the 'root.arraysList.name.floats[1]' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList.name.doubles = foo"),
+            "'double[]' is expected as a type for the 'root.arraysList.name.doubles' configuration value"
+        );
+
+        assertThrowsIllegalArgException(
+            () -> change("root.arraysList.name.strings = 10"),
+            "'String[]' is expected as a type for the 'root.arraysList.name.strings' configuration value"
+        );
+    }
+
+    /**
+     * Updates the configuration using the provided HOCON string.
+     */
+    private void change(String hocon) throws Throwable {
+        try {
+            registry.change(hoconSource(ConfigFactory.parseString(hocon).root()), null).get(1, SECONDS);
+        }
+        catch (ExecutionException e) {
+            throw e.getCause();
+        }
+    }
+
+    /** */
+    private static void assertThrowsIllegalArgException(Executable executable, String msg) {
+        IllegalArgumentException e = assertThrows(IllegalArgumentException.class, executable);
+
+        assertThat(e.getMessage(), containsString(msg));
+    }
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/json/JsonConverterTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/json/JsonConverterTest.java
index 51ee93a..deeb07c 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/json/JsonConverterTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/json/JsonConverterTest.java
@@ -31,6 +31,7 @@
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.internal.configuration.ConfigurationRegistry;
+import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/json/TestConfigurationStorage.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/json/TestConfigurationStorage.java
deleted file mode 100644
index 4b916d6..0000000
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/json/TestConfigurationStorage.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.json;
-
-import java.io.Serializable;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import org.apache.ignite.configuration.annotation.ConfigurationType;
-import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
-import org.apache.ignite.internal.configuration.storage.ConfigurationStorageListener;
-import org.apache.ignite.internal.configuration.storage.Data;
-import org.apache.ignite.internal.configuration.storage.StorageException;
-
-/** */
-class TestConfigurationStorage implements ConfigurationStorage {
-    /** */
-    private final Set<ConfigurationStorageListener> listeners = new HashSet<>();
-
-    /** {@inheritDoc} */
-    @Override public Data readAll() throws StorageException {
-        return new Data(Collections.emptyMap(), 0);
-    }
-
-    /** {@inheritDoc} */
-    @Override public CompletableFuture<Boolean> write(Map<String, Serializable> newValues, long version) {
-        for (ConfigurationStorageListener listener : listeners)
-            listener.onEntriesChanged(new Data(newValues, version + 1));
-
-        return CompletableFuture.completedFuture(true);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void registerConfigurationListener(ConfigurationStorageListener listener) {
-        listeners.add(listener);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void notifyApplied(long storageRevision) {
-    }
-
-    /** {@inheritDoc} */
-    @Override public ConfigurationType type() {
-        return ConfigurationType.LOCAL;
-    }
-}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/TraversableTreeNodeTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/TraversableTreeNodeTest.java
index 1894f6a..31a42a7 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/TraversableTreeNodeTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/TraversableTreeNodeTest.java
@@ -41,6 +41,7 @@
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNotSame;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 /** */
@@ -197,8 +198,14 @@
 
         assertNotNull(elementNode);
 
+        assertSame(elementNode, elementsNode.get(0));
+
         assertNull(elementNode.strCfg());
 
+        assertThrows(IndexOutOfBoundsException.class, () -> elementsNode.get(-1));
+
+        assertThrows(IndexOutOfBoundsException.class, () -> elementsNode.get(1));
+
         elementsNode.createOrUpdate("keyPut", element -> element.changeStrCfg("val"));
 
         // Assert that consecutive put methods create new object every time.
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java
index 3371295..ba42132 100644
--- a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java
@@ -88,7 +88,8 @@
      * @return {@code this} for chaining.
      *
      * @throws NullPointerException If key is null.
-     * @throws IllegalArgumentException If {@link #createOrUpdate(String, Consumer)} has been invoked with the same key previously.
+     * @throws IllegalArgumentException If {@link #createOrUpdate(String, Consumer)} has been invoked with the same key
+     *      previously.
      */
     NamedListChange<Change> delete(String key);
 }
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListView.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListView.java
index 60d8e9c..c422cb3 100644
--- a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListView.java
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListView.java
@@ -41,6 +41,15 @@
     View get(String key);
 
     /**
+     * Returns value located at the specified index.
+     *
+     * @param index Value index.
+     * @return Requested value.
+     * @throws IndexOutOfBoundsException If index is out of bounds.
+     */
+    View get(int index) throws IndexOutOfBoundsException;
+
+    /**
      * Returns the number of elements in this list.
      *
      * @return Number of elements.
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/NamedConfigValue.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/NamedConfigValue.java
index b5752b2..1746135 100644
--- a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/NamedConfigValue.java
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/NamedConfigValue.java
@@ -41,8 +41,30 @@
  * }
  * </code></pre>
  */
-@Target({ FIELD })
+@Target(FIELD)
 @Retention(RUNTIME)
 @Documented
 public @interface NamedConfigValue {
+    /**
+     * Key that can be used in HOCON configuration syntax to declare named list with fixed order.
+     * <pre><code>
+     * {
+     *     root : {
+     *         namedList : [
+     *             {
+     *                 syntheticKey : Element1,
+     *                 someValue = Value1
+     *             },
+     *             {
+     *                 syntheticKey : Element2,
+     *                 someValue = Value2
+     *             }
+     *         ]
+     *     }
+     * }
+     * </code></pre>
+     *
+     * @return Name for the synthetic key.
+     */
+    String syntheticKeyName() default "name";
 }
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/validation/Max.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/validation/Max.java
index 8a4f19a..70a8e47 100644
--- a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/validation/Max.java
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/validation/Max.java
@@ -25,7 +25,7 @@
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 /**
- * Signifies that this value has upper limit (exclusive).
+ * Signifies that this value has upper limit (inclusive).
  */
 @Target(FIELD)
 @Retention(RUNTIME)
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/validation/Min.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/validation/Min.java
index 54b1ac4..e5e82dc 100644
--- a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/validation/Min.java
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/validation/Min.java
@@ -25,7 +25,7 @@
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 /**
- * Signifies that this value has lower limit (exclusive).
+ * Signifies that this value has lower limit (inclusive).
  */
 @Target(FIELD)
 @Retention(RUNTIME)
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
index 1cb81e4..9885e4a 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
@@ -24,14 +24,12 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
 import com.typesafe.config.ConfigFactory;
-import com.typesafe.config.ConfigRenderOptions;
+import com.typesafe.config.ConfigObject;
 import org.apache.ignite.configuration.RootKey;
 import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.validation.Validator;
-import org.apache.ignite.internal.configuration.json.JsonConverter;
+import org.apache.ignite.internal.configuration.hocon.HoconConverter;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 
 /**
@@ -84,18 +82,16 @@
 
     /**
      * Bootstrap configuration manager with customer user cfg.
+     *
      * @param hoconStr Customer configuration in hocon format.
      * @param type Configuration type.
      * @throws InterruptedException If thread is interrupted during bootstrap.
      * @throws ExecutionException If configuration update failed for some reason.
      */
     public void bootstrap(String hoconStr, ConfigurationType type) throws InterruptedException, ExecutionException {
-        // TODO https://issues.apache.org/jira/browse/IGNITE-14924 Implement HoconConfigurationSource
-        String jsonStr = ConfigFactory.parseString(hoconStr).root().render(ConfigRenderOptions.concise());
+        ConfigObject hoconCfg = ConfigFactory.parseString(hoconStr).root();
 
-        JsonObject jsonCfg = JsonParser.parseString(jsonStr).getAsJsonObject();
-
-        confRegistry.change(JsonConverter.jsonSource(jsonCfg), configurationStorages.get(type)).get();
+        confRegistry.change(HoconConverter.hoconSource(hoconCfg), configurationStorages.get(type)).get();
     }
 
     /**
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
index 37351a6..9848272 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
@@ -450,12 +450,18 @@
             if (!isNamedConfigValue(schemaField))
                 continue;
 
+            NamedConfigValue namedCfgAnnotation = schemaField.getAnnotation(NamedConfigValue.class);
+
             SchemaClassesInfo fieldClassNames = new SchemaClassesInfo(schemaField.getType());
 
-            // this.values = new NamedListNode<>(ValueNode::new);
+            // this.values = new NamedListNode<>(key, ValueNode::new);
             ctor.getBody().append(ctor.getThis().setField(
                 fieldDefs.get(schemaField.getName()),
-                newInstance(NamedListNode.class, newNamedListElementLambda(fieldClassNames.nodeClassName))
+                newInstance(
+                    NamedListNode.class,
+                    constantString(namedCfgAnnotation.syntheticKeyName()),
+                    newNamedListElementLambda(fieldClassNames.nodeClassName)
+                )
             ));
         }
 
@@ -720,15 +726,21 @@
                     )
                 );
             }
-            // this.field = src == null ? new NamedListNode(ValueNode::new) : src.descend(field = field.copy()));
+            // this.field = src == null ? new NamedListNode<>(key, ValueNode::new) : src.descend(field = field.copy()));
             else {
+                NamedConfigValue namedCfgAnnotation = schemaField.getAnnotation(NamedConfigValue.class);
+
                 String fieldNodeClassName = schemasInfo.get(schemaField.getType()).nodeClassName;
 
                 caseClause.append(new IfStatement()
                     .condition(isNull(srcVar))
                     .ifTrue(constructMtd.getThis().setField(
                         fieldDef,
-                        newInstance(NamedListNode.class, newNamedListElementLambda(fieldNodeClassName))
+                        newInstance(
+                            NamedListNode.class,
+                            constantString(namedCfgAnnotation.syntheticKeyName()),
+                            newNamedListElementLambda(fieldNodeClassName)
+                        )
                     ))
                     .ifFalse(new BytecodeBlock()
                         .append(constructMtd.getThis().setField(
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconConfigurationVisitor.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconConfigurationVisitor.java
new file mode 100644
index 0000000..7485792
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconConfigurationVisitor.java
@@ -0,0 +1,122 @@
+/*
+ * 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.hocon;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import com.typesafe.config.ConfigValue;
+import org.apache.ignite.internal.configuration.tree.ConfigurationVisitor;
+import org.apache.ignite.internal.configuration.tree.InnerNode;
+import org.apache.ignite.internal.configuration.tree.NamedListNode;
+
+/**
+ * {@link ConfigurationVisitor} implementation that converts a configuration tree to a combination of {@link Map} and
+ * {@link List} objects to convert the result into HOCON {@link ConfigValue} instance.
+ */
+class HoconConfigurationVisitor implements ConfigurationVisitor<Object> {
+    /** Stack with intermediate results. Used to store values during recursive calls. */
+    private Deque<Object> deque = new ArrayDeque<>();
+
+    /** {@inheritDoc} */
+    @Override public Object visitLeafNode(String key, Serializable val) {
+        Object valObj = val;
+
+        if (val instanceof Character)
+            valObj = val.toString();
+        else if (val != null && val.getClass().isArray())
+            valObj = toListOfObjects(val);
+
+        addToParent(key, valObj);
+
+        return valObj;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object visitInnerNode(String key, InnerNode node) {
+        Map<String, Object> innerMap = new HashMap<>();
+
+        deque.push(innerMap);
+
+        node.traverseChildren(this);
+
+        deque.pop();
+
+        addToParent(key, innerMap);
+
+        return innerMap;
+    }
+
+    /** {@inheritDoc} */
+    @Override public <N extends InnerNode> Object visitNamedListNode(String key, NamedListNode<N> node) {
+        List<Object> list = new ArrayList<>(node.size());
+
+        deque.push(list);
+
+        for (String subkey : node.namedListKeys()) {
+            node.get(subkey).accept(subkey, this);
+
+            ((Map<String, Object>)list.get(list.size() - 1)).put(node.syntheticKeyName(), subkey);
+        }
+
+        deque.pop();
+
+        addToParent(key, list);
+
+        return list;
+    }
+
+    /**
+     * Adds a sub-element to the parent object if it exists.
+     *
+     * @param key Key for the passed element
+     * @param val Value to add to the parent
+     */
+    private void addToParent(String key, Object val) {
+        Object parent = deque.peek();
+
+        if (parent instanceof Map)
+            ((Map<String, Object>)parent).put(key, val);
+        else if (parent instanceof List)
+            ((Collection<Object>)parent).add(val);
+    }
+
+    /**
+     * Converts array into a list of objects. Boxes array elements if they are primitive values.
+     *
+     * @param val Array of primitives or array of {@link String}s
+     * @return List of objects corresponding to the passed array.
+     */
+    private List<?> toListOfObjects(Serializable val) {
+        Stream<?> stream = IntStream.range(0, Array.getLength(val)).mapToObj(i -> Array.get(val, i));
+
+        if (val.getClass().getComponentType() == char.class)
+            stream = stream.map(Object::toString);
+
+        return stream.collect(Collectors.toList());
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconConverter.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconConverter.java
new file mode 100644
index 0000000..5ce3bd2
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconConverter.java
@@ -0,0 +1,53 @@
+/*
+ * 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.hocon;
+
+import java.util.List;
+import com.typesafe.config.ConfigObject;
+import com.typesafe.config.ConfigValue;
+import com.typesafe.config.impl.ConfigImpl;
+import org.apache.ignite.internal.configuration.ConfigurationRegistry;
+import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
+import org.jetbrains.annotations.NotNull;
+
+public class HoconConverter {
+    /**
+     * Converts configuration subtree to a HOCON {@link ConfigValue} instance.
+     *
+     * @param registry Configuration registry instance.
+     * @param path Path to the configuration subtree. Can be empty, can't be {@code null}.
+     * @return {@link ConfigValue} instance that represents configuration subtree.
+     * @throws IllegalArgumentException If {@code path} is not found in current configuration.
+     */
+    public static ConfigValue represent(
+        ConfigurationRegistry registry,
+        @NotNull List<String> path
+    ) throws IllegalArgumentException {
+        Object res = registry.represent(path, new HoconConfigurationVisitor());
+
+        return ConfigImpl.fromAnyRef(res, null);
+    }
+
+    /**
+     * @param hoconCfg HOCON that has to be converted to the configuration source.
+     * @return HOCON-based configuration source.
+     */
+    public static ConfigurationSource hoconSource(ConfigObject hoconCfg) {
+        return new HoconObjectConfigurationSource(null, List.of(), hoconCfg);
+    }
+}
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
new file mode 100644
index 0000000..0deb215
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconListConfigurationSource.java
@@ -0,0 +1,149 @@
+/*
+ * 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.hocon;
+
+import java.lang.reflect.Array;
+import java.util.Iterator;
+import java.util.List;
+import com.typesafe.config.ConfigList;
+import com.typesafe.config.ConfigObject;
+import com.typesafe.config.ConfigValue;
+import com.typesafe.config.ConfigValueType;
+import org.apache.ignite.internal.configuration.TypeUtils;
+import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
+import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
+import org.apache.ignite.internal.configuration.tree.NamedListNode;
+
+import static java.lang.String.format;
+import static org.apache.ignite.internal.configuration.hocon.HoconPrimitiveConfigurationSource.formatArrayPath;
+import static org.apache.ignite.internal.configuration.hocon.HoconPrimitiveConfigurationSource.unwrapPrimitive;
+import static org.apache.ignite.internal.configuration.hocon.HoconPrimitiveConfigurationSource.wrongTypeException;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.appendKey;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.join;
+
+/**
+ * {@link ConfigurationSource} created from a HOCON list.
+ */
+class HoconListConfigurationSource implements ConfigurationSource {
+    /**
+     * Current path inside the top-level HOCON object.
+     */
+    private final List<String> path;
+
+    /**
+     * HOCON list that this source has been created from.
+     */
+    private final ConfigList hoconCfgList;
+
+    /**
+     * Creates a {@link ConfigurationSource} from the given HOCON list.
+     *
+     * @param path Current path inside the top-level HOCON object. Can be empty if the given {@code hoconCfgList}
+     *             is the top-level object.
+     * @param hoconCfgList HOCON list.
+     */
+    HoconListConfigurationSource(List<String> path, ConfigList hoconCfgList) {
+        this.path = path;
+        this.hoconCfgList = hoconCfgList;
+    }
+
+    /** {@inheritDoc} */
+    @Override public <T> T unwrap(Class<T> clazz) {
+        if (!clazz.isArray())
+            throw wrongTypeException(clazz, path, -1);
+
+        int size = hoconCfgList.size();
+
+        Class<?> componentType = clazz.getComponentType();
+        Class<?> boxedComponentType = box(componentType);
+
+        Object resArray = Array.newInstance(componentType, size);
+
+        int idx = 0;
+        for (Iterator<ConfigValue> iterator = hoconCfgList.iterator(); iterator.hasNext(); idx++) {
+            ConfigValue hoconCfgListElement = iterator.next();
+
+            switch (hoconCfgListElement.valueType()) {
+                case OBJECT:
+                case LIST:
+                    throw wrongTypeException(boxedComponentType, path, idx);
+
+                default:
+                    Array.set(resArray, idx, unwrapPrimitive(hoconCfgListElement, boxedComponentType, path, idx));
+            }
+        }
+
+        return (T)resArray;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void descend(ConstructableTreeNode node) {
+        if (!(node instanceof NamedListNode)) {
+            throw new IllegalArgumentException(
+                format("'%s' configuration is expected to be a composite configuration node, not a list", join(path))
+            );
+        }
+
+        String syntheticKeyName = ((NamedListNode<?>)node).syntheticKeyName();
+
+        int idx = 0;
+        for (Iterator<ConfigValue> iterator = hoconCfgList.iterator(); iterator.hasNext(); idx++) {
+            ConfigValue next = iterator.next();
+
+            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)
+                    )
+                );
+            }
+
+            ConfigObject hoconCfg = (ConfigObject)next;
+
+            ConfigValue keyValue = hoconCfg.get(syntheticKeyName);
+
+            if (keyValue == null || keyValue.valueType() != ConfigValueType.STRING) {
+                throw new IllegalArgumentException(
+                    format(
+                        "'%s' configuration value is mandatory and must be a String",
+                        formatArrayPath(path, idx) + "." + syntheticKeyName
+                    )
+                );
+            }
+
+            String key = (String)keyValue.unwrapped();
+
+            List<String> path = appendKey(this.path, syntheticKeyName);
+
+            node.construct(key, new HoconObjectConfigurationSource(syntheticKeyName, path, hoconCfg));
+        }
+    }
+
+    /**
+     * Non-null wrapper over {@link TypeUtils#boxed}.
+     *
+     * @param clazz Class, either primitive or not.
+     * @return Boxed version of passed class.
+     */
+    public static Class<?> box(Class<?> clazz) {
+        Class<?> boxed = TypeUtils.boxed(clazz);
+
+        return boxed == null ? clazz : boxed;
+    }
+}
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
new file mode 100644
index 0000000..ce6f14c
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconObjectConfigurationSource.java
@@ -0,0 +1,127 @@
+/*
+ * 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.hocon;
+
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import com.typesafe.config.ConfigList;
+import com.typesafe.config.ConfigObject;
+import com.typesafe.config.ConfigValue;
+import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
+import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
+import org.jetbrains.annotations.Nullable;
+
+import static java.lang.String.format;
+import static org.apache.ignite.internal.configuration.hocon.HoconPrimitiveConfigurationSource.wrongTypeException;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.appendKey;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.join;
+
+/**
+ * {@link ConfigurationSource} created from a HOCON object.
+ */
+class HoconObjectConfigurationSource implements ConfigurationSource {
+    /**
+     * Key that needs to be ignored by the source. Can be {@code null}.
+     */
+    private final String ignoredKey;
+
+    /**
+     * Current path inside the top-level HOCON object.
+     */
+    private final List<String> path;
+
+    /**
+     * HOCON object that this source has been created from.
+     */
+    private final ConfigObject hoconCfgObject;
+
+    /**
+     * Creates a {@link ConfigurationSource} from the given HOCON object.
+     *
+     * @param ignoredKey Key that needs to be ignored by the source. Can be {@code null}.
+     * @param path Current path inside the top-level HOCON object. Can be empty if the given {@code hoconCfgObject}
+     *             is the top-level object.
+     * @param hoconCfgObject HOCON object.
+     */
+    HoconObjectConfigurationSource(@Nullable String ignoredKey, List<String> path, ConfigObject hoconCfgObject) {
+        this.ignoredKey = ignoredKey;
+        this.path = path;
+        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();
+
+            if (key.equals(ignoredKey))
+                continue;
+
+            ConfigValue hoconCfgValue = entry.getValue();
+
+            try {
+                switch (hoconCfgValue.valueType()) {
+                    case NULL:
+                        node.construct(key, null);
+
+                        break;
+
+                    case OBJECT: {
+                        List<String> path = appendKey(this.path, key);
+
+                        node.construct(key, new HoconObjectConfigurationSource(null, path, (ConfigObject)hoconCfgValue));
+
+                        break;
+                    }
+
+                    case LIST: {
+                        List<String> path = appendKey(this.path, key);
+
+                        node.construct(key, new HoconListConfigurationSource(path, (ConfigList)hoconCfgValue));
+
+                        break;
+                    }
+
+                    default: {
+                        List<String> path = appendKey(this.path, key);
+
+                        node.construct(key, new HoconPrimitiveConfigurationSource(path, hoconCfgValue));
+                    }
+                }
+            }
+            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
+                    );
+                }
+            }
+        }
+    }
+}
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
new file mode 100644
index 0000000..6362861
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconPrimitiveConfigurationSource.java
@@ -0,0 +1,199 @@
+/*
+ * 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.hocon;
+
+import java.util.List;
+import com.typesafe.config.ConfigValue;
+import org.apache.ignite.internal.configuration.TypeUtils;
+import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
+import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
+
+import static com.typesafe.config.ConfigValueType.BOOLEAN;
+import static com.typesafe.config.ConfigValueType.NUMBER;
+import static com.typesafe.config.ConfigValueType.STRING;
+import static java.lang.String.format;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.join;
+
+/**
+ * {@link ConfigurationSource} created from a HOCON element representing a primitive type.
+ */
+class HoconPrimitiveConfigurationSource implements ConfigurationSource {
+    /**
+     * Current path inside the top-level HOCON object.
+     */
+    private final List<String> path;
+
+    /**
+     * HOCON object that this source has been created from.
+     */
+    private final ConfigValue hoconCfgValue;
+
+    /**
+     * Creates a {@link ConfigurationSource} from the given HOCON object representing a primitive type.
+     *
+     * @param path current path inside the top-level HOCON object. Can be empty if the given {@code hoconCfgValue}
+     *             is the top-level object
+     * @param hoconCfgValue HOCON object
+     */
+    HoconPrimitiveConfigurationSource(List<String> path, ConfigValue hoconCfgValue) {
+        assert !path.isEmpty();
+
+        this.path = path;
+        this.hoconCfgValue = hoconCfgValue;
+    }
+
+    /** {@inheritDoc} */
+    @Override public <T> T unwrap(Class<T> clazz) {
+        if (clazz.isArray())
+            throw wrongTypeException(clazz, path, -1);
+
+        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))
+        );
+    }
+
+    /**
+     * Returns exception with the message that a value is expected to be of a specific type.
+     *
+     * @param clazz Expected type of the value.
+     * @param path Path to the value.
+     * @param idx Index in the array if the value is an array element. {@code -1} if it's not.
+     * @return New {@link IllegalArgumentException} instance.
+     */
+    public static IllegalArgumentException wrongTypeException(Class<?> clazz, List<String> path, int idx) {
+        return new IllegalArgumentException(format(
+            "'%s' is expected as a type for the '%s' configuration value",
+            unbox(clazz).getSimpleName(), formatArrayPath(path, idx)
+        ));
+    }
+
+    /**
+     * Non-null wrapper over {@link TypeUtils#unboxed}.
+     *
+     * @param clazz Class for non-primitive objects.
+     * @return Unboxed class fox boxed classes, same object otherwise.
+     */
+    private static Class<?> unbox(Class<?> clazz) {
+        assert !clazz.isPrimitive();
+
+        Class<?> unboxed = TypeUtils.unboxed(clazz);
+
+        return unboxed == null ? clazz : unboxed;
+    }
+
+    /**
+     * Extracts the value from the given {@link ConfigValue} based on the expected type.
+     *
+     * @param hoconCfgValue Configuration value to unwrap.
+     * @param clazz Class that signifies resulting type of the value. Boxed primitive or String.
+     * @param path Path to the value, used for error messages.
+     * @param idx Index in the array if the value is an array element. {@code -1} if it's not.
+     * @param <T> Type of the resulting unwrapped object.
+     * @return Unwrapped object.
+     * @throws IllegalArgumentException In case of type mismatch or numeric overflow.
+     */
+    public static <T> T unwrapPrimitive(ConfigValue hoconCfgValue, Class<T> clazz, List<String> path, int idx) {
+        assert !clazz.isArray();
+        assert !clazz.isPrimitive();
+
+        if (clazz == String.class) {
+            if (hoconCfgValue.valueType() != STRING)
+                throw wrongTypeException(clazz, path, idx);
+
+            return clazz.cast(hoconCfgValue.unwrapped());
+        }
+        else if (clazz == Boolean.class) {
+            if (hoconCfgValue.valueType() != BOOLEAN)
+                throw wrongTypeException(clazz, path, idx);
+
+            return clazz.cast(hoconCfgValue.unwrapped());
+        }
+        else if (clazz == Character.class) {
+            if (hoconCfgValue.valueType() != STRING || hoconCfgValue.unwrapped().toString().length() != 1)
+                throw wrongTypeException(clazz, path, idx);
+
+            return clazz.cast(hoconCfgValue.unwrapped().toString().charAt(0));
+        }
+        else if (Number.class.isAssignableFrom(clazz)) {
+            if (hoconCfgValue.valueType() != NUMBER)
+                throw wrongTypeException(clazz, path, idx);
+
+            Number numberValue = (Number)hoconCfgValue.unwrapped();
+
+            if (clazz == Byte.class) {
+                checkBounds(numberValue, Byte.MIN_VALUE, Byte.MAX_VALUE, path, idx);
+
+                return clazz.cast(numberValue.byteValue());
+            }
+            else if (clazz == Short.class) {
+                checkBounds(numberValue, Short.MIN_VALUE, Short.MAX_VALUE, path, idx);
+
+                return clazz.cast(numberValue.shortValue());
+            }
+            else if (clazz == Integer.class) {
+                checkBounds(numberValue, Integer.MIN_VALUE, Integer.MAX_VALUE, path, idx);
+
+                return clazz.cast(numberValue.intValue());
+            }
+            else if (clazz == Long.class)
+                return clazz.cast(numberValue.longValue());
+            else if (clazz == Float.class)
+                return clazz.cast(numberValue.floatValue());
+            else if (clazz == Double.class)
+                return clazz.cast(numberValue.doubleValue());
+        }
+
+        throw new IllegalArgumentException("Unsupported type: " + clazz);
+    }
+
+    /**
+     * Checks that a number fits into the given bounds.
+     *
+     * @param numberValue Number value to validate.
+     * @param lower Lower bound, inclusive.
+     * @param upper Upper bound, inclusive.
+     * @param path Path to the value, used for error messages.
+     * @param idx Index in the array if the value is an array element. {@code -1} if it's not.
+     */
+    private static void checkBounds(Number numberValue, long lower, long upper, List<String> path, int idx) {
+        long longValue = numberValue.longValue();
+
+        if (longValue < lower || longValue > upper) {
+            throw new IllegalArgumentException(format(
+                "Value '%d' of '%s' is out of its declared bounds: [%d : %d]",
+                longValue, formatArrayPath(path, idx), lower, upper
+            ));
+        }
+    }
+
+    /**
+     * Creates a string representation of the current HOCON path inside of an array.
+     *
+     * @param path Path to the value.
+     * @param idx Index in the array if the value is an array element. {@code -1} if it's not.
+     * @return Path in a proper format.
+     */
+    public static String formatArrayPath(List<String> path, int idx) {
+        return join(path) + (idx == -1 ? "" : ("[" + idx + "]"));
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
index 0cd3874..6a90b15 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
@@ -23,6 +23,7 @@
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 import org.apache.ignite.configuration.NamedListChange;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
 
 /**
  * Configuration node implementation for the collection of named {@link InnerNode}s. Unlike implementations of
@@ -34,6 +35,9 @@
     /** Name of a synthetic configuration property that describes the order of elements in a named list. */
     public static final String ORDER_IDX = "<idx>";
 
+    /** Configuration name for the synthetic key. */
+    private final String syntheticKeyName;
+
     /** Supplier of new node objects when new list element node has to be created. */
     private final Supplier<N> valSupplier;
 
@@ -43,9 +47,12 @@
     /**
      * Default constructor.
      *
+     * @param syntheticKeyName Name of the synthetic configuration value that will represent keys in a specially ordered
+     *      representation syntax.
      * @param valSupplier Closure to instantiate values.
      */
-    public NamedListNode(Supplier<N> valSupplier) {
+    public NamedListNode(String syntheticKeyName, Supplier<N> valSupplier) {
+        this.syntheticKeyName = syntheticKeyName;
         this.valSupplier = valSupplier;
         map = new OrderedMap<>();
     }
@@ -56,6 +63,7 @@
      * @param node Other node.
      */
     private NamedListNode(NamedListNode<N> node) {
+        syntheticKeyName = node.syntheticKeyName;
         valSupplier = node.valSupplier;
         map = new OrderedMap<>(node.map);
     }
@@ -76,6 +84,11 @@
     }
 
     /** {@inheritDoc} */
+    @Override public N get(int index) throws IndexOutOfBoundsException {
+        return map.get(index);
+    }
+
+    /** {@inheritDoc} */
     @Override public int size() {
         return map.size();
     }
@@ -180,6 +193,15 @@
     }
 
     /**
+     * @return Configuration name for the synthetic key.
+     *
+     * @see NamedConfigValue#syntheticKeyName()
+     */
+    public String syntheticKeyName() {
+        return syntheticKeyName;
+    }
+
+    /**
      * Deletes named list element.
      *
      * @param key Element's key.
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/OrderedMap.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/OrderedMap.java
index d67d3f3..024963a 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/OrderedMap.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/OrderedMap.java
@@ -72,6 +72,17 @@
     }
 
     /**
+     * Returns value located at the specified index.
+     *
+     * @param index Value index.
+     * @return Requested value.
+     * @throws IndexOutOfBoundsException If index is out of bounds.
+     */
+    public V get(int index) {
+        return map.get(orderedKeys.get(index));
+    }
+
+    /**
      * Same as {@link Map#remove(Object)}.
      *
      * @param key Key to remove.
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/OrderedMapTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/OrderedMapTest.java
index 580d181..8863d89 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/OrderedMapTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/OrderedMapTest.java
@@ -23,6 +23,7 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 /** Test with basic {@link OrderedMap} invariants. */
 public class OrderedMapTest {
@@ -38,10 +39,13 @@
 
         assertEquals(1, map.size());
         assertEquals("value", map.get("key"));
+        assertEquals("value", map.get(0));
 
         map.remove("key");
 
         assertNull(map.get("key1"));
+
+        assertThrows(IndexOutOfBoundsException.class, () -> map.get(0));
     }
 
     /** Tests that {@link OrderedMap#put(String, Object)} preserves order. */