You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ib...@apache.org on 2021/09/01 13:44:43 UTC

[ignite-3] branch main updated: IGNITE-15047 Implemented internal configuration roots and extensions. (#290)

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

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


The following commit(s) were added to refs/heads/main by this push:
     new 2fa8a85  IGNITE-15047 Implemented internal configuration roots and extensions. (#290)
2fa8a85 is described below

commit 2fa8a85a5de3bf7f4946b37c6fa098e85d722e60
Author: Kirill Tkalenko <tk...@yandex.ru>
AuthorDate: Wed Sep 1 16:44:40 2021 +0300

    IGNITE-15047 Implemented internal configuration roots and extensions. (#290)
---
 .../internal/affinity/AffinityManagerTest.java     |   3 +-
 .../facebook/presto/bytecode/ClassDefinition.java  |  14 +-
 .../handler/ClientHandlerIntegrationTest.java      |   8 +-
 .../apache/ignite/client/AbstractClientTest.java   |   3 +-
 .../configuration/processor/ITProcessorTest.java   |  88 +++-
 .../ErrorInternal0ConfigurationSchema.java         |  29 +
 .../ErrorInternal1ConfigurationSchema.java         |  30 ++
 .../ErrorInternal2ConfigurationSchema.java         |  29 +
 .../ErrorInternal3ConfigurationSchema.java         |  27 +
 .../ErrorInternal4ConfigurationSchema.java         |  30 ++
 .../ErrorInternal5ConfigurationSchema.java         |  31 ++
 .../ExtendedSimpleConfigurationSchema.java         |  31 ++
 .../ExtendedSimpleRootConfigurationSchema.java     |  31 ++
 .../internal/SimpleConfigurationSchema.java        |  35 ++
 .../internal/SimpleRootConfigurationSchema.java    |  41 ++
 .../configuration/processor/Processor.java         | 208 ++++++--
 .../configuration/ConfigurationChangerTest.java    |  23 +-
 .../configuration/ConfigurationRegistryTest.java   |  86 +++
 .../configuration/TestConfigurationChanger.java    |  13 +-
 .../asm/ConfigurationAsmGeneratorTest.java         | 309 +++++++++++
 .../configuration/hocon/HoconConverterTest.java    |   3 +-
 .../notifications/ConfigurationListenerTest.java   |   7 +-
 .../internal/configuration/sample/UsageTest.java   |   6 +-
 .../configuration/tree/ConfigurationArrayTest.java |   5 +-
 .../tree/ConstructableTreeNodeTest.java            |  25 +-
 .../configuration/tree/NamedListNodeTest.java      |   2 +-
 .../tree/TraversableTreeNodeTest.java              |  24 +-
 .../configuration/util/ConfigurationUtilTest.java  | 388 +++++++++++++-
 .../validation/ValidationUtilTest.java             |   2 +-
 .../org/apache/ignite/configuration/RootKey.java   |  15 +
 .../annotation/InternalConfiguration.java          |  42 ++
 .../configuration/ConfigurationChanger.java        |   4 +-
 .../configuration/ConfigurationManager.java        |  13 +-
 .../internal/configuration/ConfigurationNode.java  |   2 +-
 .../configuration/ConfigurationRegistry.java       |  48 +-
 .../configuration/DynamicConfiguration.java        |   5 +-
 .../internal/configuration/DynamicProperty.java    |   6 +-
 .../internal/configuration/RootInnerNode.java      |  71 +++
 .../ignite/internal/configuration/SuperRoot.java   | 111 ++--
 .../asm/ConfigurationAsmGenerator.java             | 585 ++++++++++++++-------
 .../configuration/asm/SchemaClassesInfo.java       |  64 ++-
 .../configuration/hocon/HoconConverter.java        |   3 +-
 .../hocon/HoconListConfigurationSource.java        |   2 +-
 .../hocon/HoconObjectConfigurationSource.java      |  12 +-
 .../configuration/tree/ConstructableTreeNode.java  |   3 +-
 .../ConverterToMapVisitor.java}                    |  26 +-
 .../internal/configuration/tree/InnerNode.java     | 120 +++--
 .../internal/configuration/tree/NamedListNode.java |   2 +-
 .../configuration/util/ConfigurationFlattener.java |  14 +-
 .../util/ConfigurationNotificationsUtil.java       |   8 +-
 .../configuration/util/ConfigurationUtil.java      | 263 ++++++++-
 .../util/KeysTrackingConfigurationVisitor.java     |   2 +-
 .../validation/ValidationContextImpl.java          |   2 +-
 .../configuration/validation/ValidationUtil.java   |   4 +-
 .../ignite/internal/util/CollectionUtils.java      |  72 +++
 .../ignite/internal/util/CollectionUtilsTest.java  |  74 +++
 .../ignite/utils/ClusterServiceTestUtils.java      |   4 +-
 .../ConfigurationPresentationTest.java             |   3 +-
 .../ITDistributedConfigurationStorageTest.java     |   3 +-
 .../org/apache/ignite/internal/app/IgniteImpl.java |   6 +-
 .../SchemaConfigurationConverterTest.java          |   3 +-
 .../ignite/internal/table/TableManagerTest.java    |   6 +-
 62 files changed, 2657 insertions(+), 472 deletions(-)

diff --git a/modules/affinity/src/test/java/org/apache/ignite/internal/affinity/AffinityManagerTest.java b/modules/affinity/src/test/java/org/apache/ignite/internal/affinity/AffinityManagerTest.java
index 152c900..298211c 100644
--- a/modules/affinity/src/test/java/org/apache/ignite/internal/affinity/AffinityManagerTest.java
+++ b/modules/affinity/src/test/java/org/apache/ignite/internal/affinity/AffinityManagerTest.java
@@ -84,7 +84,8 @@ public class AffinityManagerTest {
             cfrMgr = new ConfigurationManager(
                 rootConfigurationKeys(),
                 Map.of(),
-                new TestConfigurationStorage(DISTRIBUTED)
+                new TestConfigurationStorage(DISTRIBUTED),
+                List.of()
             );
 
             cfrMgr.start();
diff --git a/modules/bytecode/src/main/java/com/facebook/presto/bytecode/ClassDefinition.java b/modules/bytecode/src/main/java/com/facebook/presto/bytecode/ClassDefinition.java
index 7fa7469..74363b6 100644
--- a/modules/bytecode/src/main/java/com/facebook/presto/bytecode/ClassDefinition.java
+++ b/modules/bytecode/src/main/java/com/facebook/presto/bytecode/ClassDefinition.java
@@ -26,8 +26,10 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.objectweb.asm.ClassVisitor;
 
+import static com.facebook.presto.bytecode.Access.BRIDGE;
 import static com.facebook.presto.bytecode.Access.INTERFACE;
 import static com.facebook.presto.bytecode.Access.STATIC;
+import static com.facebook.presto.bytecode.Access.SYNTHETIC;
 import static com.facebook.presto.bytecode.Access.a;
 import static com.facebook.presto.bytecode.Access.toAccessModifier;
 import static java.util.Objects.requireNonNull;
@@ -246,11 +248,19 @@ public class ClassDefinition {
         EnumSet<Access> access,
         String name,
         ParameterizedType returnType,
-        Collection<Parameter> parameters) {
+        Collection<Parameter> parameters
+    ) {
         MethodDefinition methodDefinition = new MethodDefinition(this, access, name, returnType, parameters);
+
+        EnumSet<Access> bridgeAccess = EnumSet.of(SYNTHETIC, BRIDGE);
+
         for (MethodDefinition method : methods) {
             if (name.equals(method.getName()) && method.getParameterTypes().equals(methodDefinition.getParameterTypes())) {
-                throw new IllegalArgumentException("Method with same name and signature already exists: " + name);
+                boolean curBridge = method.getAccess().containsAll(bridgeAccess);
+                boolean newBridge = methodDefinition.getAccess().containsAll(bridgeAccess);
+
+                if ((!curBridge && !newBridge) || method.getReturnType().equals(methodDefinition.getReturnType()))
+                    throw new IllegalArgumentException("Method with same name and signature already exists: " + name);
             }
         }
         methods.add(methodDefinition);
diff --git a/modules/client-handler/src/test/java/org/apache/ignite/client/handler/ClientHandlerIntegrationTest.java b/modules/client-handler/src/test/java/org/apache/ignite/client/handler/ClientHandlerIntegrationTest.java
index 574cbbb..9aa6a28 100644
--- a/modules/client-handler/src/test/java/org/apache/ignite/client/handler/ClientHandlerIntegrationTest.java
+++ b/modules/client-handler/src/test/java/org/apache/ignite/client/handler/ClientHandlerIntegrationTest.java
@@ -177,9 +177,11 @@ public class ClientHandlerIntegrationTest {
 
     private ClientHandlerModule startServer() {
         configurationManager = new ConfigurationManager(
-                List.of(ClientConnectorConfiguration.KEY),
-                Map.of(),
-                new TestConfigurationStorage(LOCAL));
+            List.of(ClientConnectorConfiguration.KEY),
+            Map.of(),
+            new TestConfigurationStorage(LOCAL),
+            List.of()
+        );
 
         configurationManager.start();
 
diff --git a/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTest.java b/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTest.java
index 744d2e9..86859f4 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTest.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTest.java
@@ -100,7 +100,8 @@ public abstract class AbstractClientTest {
         var cfg = new ConfigurationRegistry(
             List.of(ClientConnectorConfiguration.KEY),
             Map.of(),
-            new TestConfigurationStorage(LOCAL)
+            new TestConfigurationStorage(LOCAL),
+            List.of()
         );
 
         cfg.start();
diff --git a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ITProcessorTest.java b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ITProcessorTest.java
index 9ed4db4..965f612 100644
--- a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ITProcessorTest.java
+++ b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ITProcessorTest.java
@@ -16,12 +16,14 @@
  */
 package org.apache.ignite.internal.configuration.processor;
 
+import java.util.Arrays;
 import com.google.testing.compile.Compilation;
 import com.squareup.javapoet.ClassName;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -32,7 +34,7 @@ public class ITProcessorTest extends AbstractProcessorTest {
      * The simplest test for code generation.
      */
     @Test
-    public void test() {
+    public void testPublicConfigCodeGeneration() {
         final String packageName = "org.apache.ignite.internal.configuration.processor";
 
         final ClassName testConfigurationSchema = ClassName.get(packageName, "TestConfigurationSchema");
@@ -49,4 +51,88 @@ public class ITProcessorTest extends AbstractProcessorTest {
 
         assertTrue(classSet.allGenerated());
     }
+
+    /** */
+    @Test
+    void testSuccessInternalConfigCodeGeneration() {
+        String packageName = "org.apache.ignite.internal.configuration.processor.internal";
+
+        ClassName cls0 = ClassName.get(packageName, "SimpleRootConfigurationSchema");
+        ClassName cls1 = ClassName.get(packageName, "SimpleConfigurationSchema");
+        ClassName cls2 = ClassName.get(packageName, "ExtendedSimpleRootConfigurationSchema");
+        ClassName cls3 = ClassName.get(packageName, "ExtendedSimpleConfigurationSchema");
+
+        BatchCompilation batchCompile = batchCompile(cls0, cls1, cls2, cls3);
+
+        assertNotEquals(Compilation.Status.FAILURE, batchCompile.getCompilationStatus().status());
+
+        assertEquals(4 * 3, batchCompile.generated().size());
+
+        assertTrue(batchCompile.getBySchema(cls0).allGenerated());
+        assertTrue(batchCompile.getBySchema(cls1).allGenerated());
+        assertTrue(batchCompile.getBySchema(cls2).allGenerated());
+        assertTrue(batchCompile.getBySchema(cls3).allGenerated());
+    }
+
+    /** */
+    @Test
+    void testErrorInternalConfigCodeGeneration() {
+        String packageName = "org.apache.ignite.internal.configuration.processor.internal";
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorInternal0ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorInternal1ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorInternal2ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(
+                packageName,
+                "SimpleRootConfigurationSchema",
+                "SimpleConfigurationSchema",
+                "ExtendedSimpleRootConfigurationSchema",
+                "ErrorInternal3ConfigurationSchema"
+            )
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorInternal4ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(
+                packageName,
+                "SimpleRootConfigurationSchema",
+                "SimpleConfigurationSchema",
+                "ErrorInternal5ConfigurationSchema"
+            )
+        );
+    }
+
+    /**
+     * Compile set of classes.
+     *
+     * @param packageName Package names.
+     * @param classNames Simple class names.
+     * @return Result of batch compilation.
+     */
+    private BatchCompilation batchCompile(String packageName, String... classNames) {
+        ClassName[] classes = Arrays.stream(classNames)
+            .map(clsName -> ClassName.get(packageName, clsName))
+            .toArray(ClassName[]::new);
+
+        return batchCompile(classes);
+    }
 }
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal0ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal0ConfigurationSchema.java
new file mode 100644
index 0000000..e98911c
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal0ConfigurationSchema.java
@@ -0,0 +1,29 @@
+/*
+ * 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.internal;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+
+/**
+ * Class cannot have {@link InternalConfiguration} and {@link Config}.
+ */
+@InternalConfiguration
+@Config
+public class ErrorInternal0ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal1ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal1ConfigurationSchema.java
new file mode 100644
index 0000000..de90a3b
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal1ConfigurationSchema.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.internal;
+
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+
+/**
+ * If there are {@link InternalConfiguration} and {@link ConfigurationRoot},
+ * the class should not have a parent (superclass).
+ */
+@InternalConfiguration
+@ConfigurationRoot(rootName = "errorRoot")
+public class ErrorInternal1ConfigurationSchema extends SimpleRootConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal2ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal2ConfigurationSchema.java
new file mode 100644
index 0000000..58daa49
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal2ConfigurationSchema.java
@@ -0,0 +1,29 @@
+/*
+ * 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.internal;
+
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+
+/**
+ * If {@link InternalConfiguration} is present and {@link ConfigurationRoot} is absent,
+ * the class must have a parent (superclass).
+ */
+@InternalConfiguration
+public class ErrorInternal2ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal3ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal3ConfigurationSchema.java
new file mode 100644
index 0000000..42a337d
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal3ConfigurationSchema.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.internal;
+
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+
+/**
+ * Parent (superclass) must not contain {@link InternalConfiguration}.
+ */
+@InternalConfiguration
+public class ErrorInternal3ConfigurationSchema extends ExtendedSimpleRootConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal4ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal4ConfigurationSchema.java
new file mode 100644
index 0000000..77a8306
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal4ConfigurationSchema.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.internal;
+
+import java.util.ArrayList;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+
+/**
+ * Parent (superclass) must contain either {@link Config} or {@link ConfigurationRoot}.
+ */
+@InternalConfiguration
+public class ErrorInternal4ConfigurationSchema extends ArrayList {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal5ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal5ConfigurationSchema.java
new file mode 100644
index 0000000..57b5b61
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ErrorInternal5ConfigurationSchema.java
@@ -0,0 +1,31 @@
+/*
+ * 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.internal;
+
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Extension and the parent (superclass) must not have the same fields by name.
+ */
+@InternalConfiguration
+public class ErrorInternal5ConfigurationSchema extends SimpleRootConfigurationSchema {
+    /** String value. */
+    @Value
+    public String str;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ExtendedSimpleConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ExtendedSimpleConfigurationSchema.java
new file mode 100644
index 0000000..7bfec00
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ExtendedSimpleConfigurationSchema.java
@@ -0,0 +1,31 @@
+/*
+ * 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.internal;
+
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Simple internal configuration schema.
+ */
+@InternalConfiguration
+public class ExtendedSimpleConfigurationSchema extends SimpleConfigurationSchema {
+    /** String value. */
+    @Value
+    public String exSubStr;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ExtendedSimpleRootConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ExtendedSimpleRootConfigurationSchema.java
new file mode 100644
index 0000000..7384edc
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/ExtendedSimpleRootConfigurationSchema.java
@@ -0,0 +1,31 @@
+/*
+ * 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.internal;
+
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Simple internal root configuration schema.
+ */
+@InternalConfiguration
+public class ExtendedSimpleRootConfigurationSchema extends SimpleRootConfigurationSchema {
+    /** String value. */
+    @Value
+    public String exStr;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/SimpleConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/SimpleConfigurationSchema.java
new file mode 100644
index 0000000..7d4fca4
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/SimpleConfigurationSchema.java
@@ -0,0 +1,35 @@
+/*
+ * 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.internal;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Simple configuration schema.
+ */
+@Config
+public class SimpleConfigurationSchema {
+    /** String value. */
+    @Value
+    public String subStr;
+
+    /** Int value. */
+    @Value
+    public int subInt;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/SimpleRootConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/SimpleRootConfigurationSchema.java
new file mode 100644
index 0000000..cf69726
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/internal/SimpleRootConfigurationSchema.java
@@ -0,0 +1,41 @@
+/*
+ * 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.internal;
+
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Simple root configuration schema.
+ */
+@ConfigurationRoot(rootName = "simpleRoot")
+public class SimpleRootConfigurationSchema {
+    /** String value. */
+    @Value
+    public String str;
+
+    /** Sub configuration. */
+    @ConfigValue
+    public SimpleConfigurationSchema subCfg;
+
+    /** Named configuration. */
+    @NamedConfigValue
+    public SimpleConfigurationSchema namedCfg;
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/Processor.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/Processor.java
index 43ab153..fe923d9 100644
--- a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/Processor.java
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/Processor.java
@@ -20,19 +20,20 @@ package org.apache.ignite.internal.configuration.processor;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
-import java.util.stream.Collectors;
 import javax.annotation.processing.AbstractProcessor;
 import javax.annotation.processing.RoundEnvironment;
 import javax.lang.model.SourceVersion;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ElementKind;
-import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.Name;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.element.VariableElement;
 import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
 import javax.lang.model.type.TypeKind;
 import javax.lang.model.type.TypeMirror;
 import javax.lang.model.util.Elements;
@@ -51,9 +52,13 @@ import org.apache.ignite.configuration.NamedListView;
 import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.ConfigValue;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.Value;
+import org.jetbrains.annotations.Nullable;
 
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
 import static javax.lang.model.element.Modifier.ABSTRACT;
 import static javax.lang.model.element.Modifier.FINAL;
 import static javax.lang.model.element.Modifier.PUBLIC;
@@ -84,37 +89,38 @@ public class Processor extends AbstractProcessor {
 
     /**
      * Processes a set of annotation types on type elements.
+     *
      * @param roundEnvironment Processing environment.
      * @return Whether or not the set of annotation types are claimed by this processor.
      */
     private boolean process0(RoundEnvironment roundEnvironment) {
         Elements elementUtils = processingEnv.getElementUtils();
 
-        // All classes annotated with @Config
+        // All classes annotated with @ConfigurationRoot, @Config, @InternalConfiguration.
         List<TypeElement> annotatedConfigs = roundEnvironment
-            .getElementsAnnotatedWithAny(Set.of(ConfigurationRoot.class, Config.class)).stream()
+            .getElementsAnnotatedWithAny(Set.of(ConfigurationRoot.class, Config.class, InternalConfiguration.class))
+            .stream()
             .filter(element -> element.getKind() == ElementKind.CLASS)
             .map(TypeElement.class::cast)
-            .collect(Collectors.toList());
+            .collect(toList());
 
         if (annotatedConfigs.isEmpty())
             return false;
 
         for (TypeElement clazz : annotatedConfigs) {
-            // Get package name of the schema class
-            PackageElement elementPackage = elementUtils.getPackageOf(clazz);
-            String packageName = elementPackage.getQualifiedName().toString();
+            // Find all the fields of the schema.
+            Collection<VariableElement> fields = fields(clazz);
+
+            validate(clazz, fields);
 
-            // Find all the fields of the schema
-            List<VariableElement> fields = clazz.getEnclosedElements().stream()
-                .filter(el -> el.getKind() == ElementKind.FIELD)
-                .map(VariableElement.class::cast)
-                .collect(Collectors.toList());
+            // Is root of the configuration.
+            boolean isRootConfig = clazz.getAnnotation(ConfigurationRoot.class) != null;
 
-            ConfigurationRoot rootAnnotation = clazz.getAnnotation(ConfigurationRoot.class);
+            // Is the internal configuration.
+            boolean isInternalConfig = clazz.getAnnotation(InternalConfiguration.class) != null;
 
-            // Is root of the configuration
-            boolean isRoot = rootAnnotation != null;
+            // Get package name of the schema class
+            String packageName = elementUtils.getPackageOf(clazz).getQualifiedName().toString();
 
             ClassName schemaClassName = ClassName.get(packageName, clazz.getSimpleName().toString());
 
@@ -173,10 +179,16 @@ public class Processor extends AbstractProcessor {
                 createGetters(configurationInterfaceBuilder, fieldName, interfaceGetMethodType);
             }
 
-            // Create VIEW and CHANGE classes
-            createPojoBindings(fields, schemaClassName, configurationInterfaceBuilder);
+            // Create VIEW and CHANGE classes.
+            createPojoBindings(
+                fields,
+                schemaClassName,
+                configurationInterfaceBuilder,
+                isInternalConfig && !isRootConfig,
+                clazz
+            );
 
-            if (isRoot)
+            if (isRootConfig)
                 createRootKeyField(configInterface, configurationInterfaceBuilder, schemaClassName, clazz);
 
             // Write configuration interface
@@ -273,36 +285,62 @@ public class Processor extends AbstractProcessor {
 
     /**
      * Create VIEW and CHANGE classes and methods.
+     *
      * @param fields List of configuration fields.
      * @param schemaClassName Class name of schema.
+     * @param configurationInterfaceBuilder Configuration interface builder.
+     * @param extendBaseSchema {@code true} if extending base schema interfaces.
+     * @param realSchemaClass Class descriptor.
      */
     private void createPojoBindings(
-        List<VariableElement> fields,
+        Collection<VariableElement> fields,
         ClassName schemaClassName,
-        TypeSpec.Builder configurationInterfaceBuilder
+        TypeSpec.Builder configurationInterfaceBuilder,
+        boolean extendBaseSchema,
+        TypeElement realSchemaClass
     ) {
-        ClassName viewClassTypeName = Utils.getViewName(schemaClassName);
-        ClassName changeClassName = Utils.getChangeName(schemaClassName);
+        ClassName viewClsName = Utils.getViewName(schemaClassName);
+        ClassName changeClsName = Utils.getChangeName(schemaClassName);
+
+        TypeName configInterfaceType;
+        @Nullable TypeName viewBaseSchemaInterfaceType;
+        @Nullable TypeName changeBaseSchemaInterfaceType;
+
+        if (extendBaseSchema) {
+            DeclaredType superClassType = (DeclaredType)realSchemaClass.getSuperclass();
+            ClassName superClassSchemaClassName = ClassName.get((TypeElement)superClassType.asElement());
+
+            configInterfaceType = Utils.getConfigurationInterfaceName(superClassSchemaClassName);
+            viewBaseSchemaInterfaceType = Utils.getViewName(superClassSchemaClassName);
+            changeBaseSchemaInterfaceType = Utils.getChangeName(superClassSchemaClassName);
+        }
+        else {
+            ClassName confTreeInterface = ClassName.get("org.apache.ignite.configuration", "ConfigurationTree");
+            configInterfaceType = ParameterizedTypeName.get(confTreeInterface, viewClsName, changeClsName);
 
-        ClassName confTreeInterface = ClassName.get("org.apache.ignite.configuration", "ConfigurationTree");
-        TypeName confTreeParameterized = ParameterizedTypeName.get(confTreeInterface, viewClassTypeName, changeClassName);
+            viewBaseSchemaInterfaceType = null;
+            changeBaseSchemaInterfaceType = null;
+        }
 
-        configurationInterfaceBuilder.addSuperinterface(confTreeParameterized);
+        configurationInterfaceBuilder.addSuperinterface(configInterfaceType);
 
         // This code will be refactored in the future. Right now I don't want to entangle it with existing code
         // generation. It has only a few considerable problems - hardcode and a lack of proper arrays handling.
         // Clone method should be used to guarantee data integrity.
-        ClassName viewClsName = Utils.getViewName(schemaClassName);
-
-        ClassName changeClsName = Utils.getChangeName(schemaClassName);
 
         TypeSpec.Builder viewClsBuilder = TypeSpec.interfaceBuilder(viewClsName)
             .addModifiers(PUBLIC);
 
+        if (viewBaseSchemaInterfaceType != null)
+            viewClsBuilder.addSuperinterface(viewBaseSchemaInterfaceType);
+
         TypeSpec.Builder changeClsBuilder = TypeSpec.interfaceBuilder(changeClsName)
             .addSuperinterface(viewClsName)
             .addModifiers(PUBLIC);
 
+        if (changeBaseSchemaInterfaceType != null)
+            changeClsBuilder.addSuperinterface(changeBaseSchemaInterfaceType);
+
         ClassName consumerClsName = ClassName.get(Consumer.class);
 
         for (VariableElement field : fields) {
@@ -408,9 +446,119 @@ public class Processor extends AbstractProcessor {
         return processingEnv.getTypeUtils().isSameType(type, stringType);
     }
 
+    /**
+     * Check if a class type is {@link Object}.
+     *
+     * @param type Class type.
+     * @return {@code true} if class type is {@link Object}.
+     */
+    private boolean isObjectClass(TypeMirror type) {
+        TypeMirror objectType = processingEnv
+            .getElementUtils()
+            .getTypeElement(Object.class.getCanonicalName())
+            .asType();
+
+        return objectType.equals(type);
+    }
+
+    /**
+     * Get class fields.
+     *
+     * @param type Class type.
+     * @return Class fields.
+     */
+    private Collection<VariableElement> fields(TypeElement type) {
+        return type.getEnclosedElements().stream()
+            .filter(el -> el.getKind() == ElementKind.FIELD)
+            .map(VariableElement.class::cast)
+            .collect(toList());
+    }
+
+    /**
+     * Validate the class.
+     *
+     * @param clazz Class type.
+     * @param fields Class fields.
+     * @throws ProcessorException If the class validation fails.
+     */
+    private void validate(TypeElement clazz, Collection<VariableElement> fields) {
+        if (clazz.getAnnotation(InternalConfiguration.class) != null) {
+            if (clazz.getAnnotation(Config.class) != null) {
+                throw new ProcessorException(String.format(
+                    "Class with @%s is not allowed with @%s: %s",
+                    Config.class.getSimpleName(),
+                    InternalConfiguration.class.getSimpleName(),
+                    clazz.getQualifiedName()
+                ));
+            }
+            else if (clazz.getAnnotation(ConfigurationRoot.class) != null) {
+                if (!isObjectClass(clazz.getSuperclass())) {
+                    throw new ProcessorException(String.format(
+                        "Class with @%s and @%s should not have a superclass: %s",
+                        ConfigurationRoot.class.getSimpleName(),
+                        InternalConfiguration.class.getSimpleName(),
+                        clazz.getQualifiedName()
+                    ));
+                }
+            }
+            else if (isObjectClass(clazz.getSuperclass())) {
+                throw new ProcessorException(String.format(
+                    "Class with @%s must have a superclass: %s",
+                    InternalConfiguration.class.getSimpleName(),
+                    clazz.getQualifiedName()
+                ));
+            }
+            else {
+                TypeElement superClazz = processingEnv
+                    .getElementUtils()
+                    .getTypeElement(clazz.getSuperclass().toString());
+
+                if (superClazz.getAnnotation(InternalConfiguration.class) != null) {
+                    throw new ProcessorException(String.format(
+                        "Superclass must not have @%s: %s",
+                        InternalConfiguration.class.getSimpleName(),
+                        clazz.getQualifiedName()
+                    ));
+                }
+                else if (superClazz.getAnnotation(ConfigurationRoot.class) == null &&
+                    superClazz.getAnnotation(Config.class) == null) {
+                    throw new ProcessorException(String.format(
+                        "Superclass must have @%s or @%s: %s",
+                        ConfigurationRoot.class.getSimpleName(),
+                        Config.class.getSimpleName(),
+                        clazz.getQualifiedName()
+                    ));
+                }
+                else {
+                    Set<Name> superClazzFieldNames = fields(superClazz).stream()
+                        .map(VariableElement::getSimpleName)
+                        .collect(toSet());
+
+                    Collection<Name> duplicateFieldNames = fields.stream()
+                        .map(VariableElement::getSimpleName)
+                        .filter(superClazzFieldNames::contains)
+                        .collect(toList());
+
+                    if (!duplicateFieldNames.isEmpty()) {
+                        throw new ProcessorException(String.format(
+                            "Duplicate field names are not allowed [class=%s, superClass=%s, fields=%s]",
+                            clazz.getQualifiedName(),
+                            superClazz.getQualifiedName(),
+                            duplicateFieldNames
+                        ));
+                    }
+                }
+            }
+        }
+    }
+
     /** {@inheritDoc} */
     @Override public Set<String> getSupportedAnnotationTypes() {
-        return Set.of(Config.class.getCanonicalName(), ConfigurationRoot.class.getCanonicalName());
+        return Set.of(
+            Config.class.getCanonicalName(),
+            ConfigurationRoot.class.getCanonicalName(),
+            InternalConfiguration.class.getCanonicalName()
+        );
     }
 
     /** {@inheritDoc} */
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
index b1e69fb..cd2e3c1 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
@@ -111,7 +111,7 @@ public class ConfigurationChangerTest {
     public void testSimpleConfigurationChange() throws Exception {
         var storage = new TestConfigurationStorage(LOCAL);
 
-        ConfigurationChanger changer = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage);
+        ConfigurationChanger changer = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage, List.of());
         changer.start();
 
         changer.change(source(KEY, (AChange parent) -> parent
@@ -133,10 +133,10 @@ public class ConfigurationChangerTest {
     public void testModifiedFromAnotherStorage() throws Exception {
         var storage = new TestConfigurationStorage(LOCAL);
 
-        ConfigurationChanger changer1 = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage);
+        ConfigurationChanger changer1 = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage, List.of());
         changer1.start();
 
-        ConfigurationChanger changer2 = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage);
+        ConfigurationChanger changer2 = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage, List.of());
         changer2.start();
 
         changer1.change(source(KEY, (AChange parent) -> parent
@@ -174,7 +174,7 @@ public class ConfigurationChangerTest {
     public void testModifiedFromAnotherStorageWithIncompatibleChanges() throws Exception {
         var storage = new TestConfigurationStorage(LOCAL);
 
-        ConfigurationChanger changer1 = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage);
+        ConfigurationChanger changer1 = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage, List.of());
         changer1.start();
 
         Validator<MaybeInvalid, Object> validator = new Validator<>() {
@@ -188,7 +188,8 @@ public class ConfigurationChangerTest {
             cgen,
             List.of(KEY),
             Map.of(MaybeInvalid.class, Set.of(validator)),
-            storage
+            storage,
+            List.of()
         );
 
         changer2.start();
@@ -220,7 +221,7 @@ public class ConfigurationChangerTest {
     public void testFailedToWrite() {
         var storage = new TestConfigurationStorage(LOCAL);
 
-        ConfigurationChanger changer = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage);
+        ConfigurationChanger changer = new TestConfigurationChanger(cgen, List.of(KEY), Map.of(), storage, List.of());
 
         storage.fail(true);
 
@@ -279,7 +280,13 @@ public class ConfigurationChangerTest {
     public void defaultsOnInit() throws Exception {
         var storage = new TestConfigurationStorage(LOCAL);
 
-        var changer = new TestConfigurationChanger(cgen, List.of(DefaultsConfiguration.KEY), Map.of(), storage);
+        var changer = new TestConfigurationChanger(
+            cgen,
+            List.of(DefaultsConfiguration.KEY),
+            Map.of(),
+            storage,
+            List.of()
+        );
 
         changer.start();
 
@@ -311,7 +318,7 @@ public class ConfigurationChangerTest {
                     }
                 };
 
-                node.construct(rootKey.key(), changerSrc);
+                node.construct(rootKey.key(), changerSrc, true);
             }
         };
     }
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/ConfigurationRegistryTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/ConfigurationRegistryTest.java
new file mode 100644
index 0000000..8b65be0
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/ConfigurationRegistryTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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 java.util.List;
+import java.util.Map;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
+import org.junit.jupiter.api.Test;
+
+import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Class for testing the {@link ConfigurationRegistry}.
+ */
+public class ConfigurationRegistryTest {
+    /** */
+    @Test
+    void testValidationInternalConfigurationExtensions() {
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> new ConfigurationRegistry(
+                List.of(SecondRootConfiguration.KEY),
+                Map.of(),
+                new TestConfigurationStorage(LOCAL),
+                List.of(ExtendedFirstRootConfigurationSchema.class)
+            )
+        );
+
+        // Check that everything is fine.
+        new ConfigurationRegistry(
+            List.of(FirstRootConfiguration.KEY, SecondRootConfiguration.KEY),
+            Map.of(),
+            new TestConfigurationStorage(LOCAL),
+            List.of(ExtendedFirstRootConfigurationSchema.class)
+        );
+    }
+
+    /**
+     * First root configuration.
+     */
+    @ConfigurationRoot(rootName = "first")
+    public static class FirstRootConfigurationSchema {
+        /** String field. */
+        @Value(hasDefault = true)
+        public String str = "str";
+    }
+
+    /**
+     * First root configuration.
+     */
+    @ConfigurationRoot(rootName = "second")
+    public static class SecondRootConfigurationSchema {
+        /** String field. */
+        @Value(hasDefault = true)
+        public String str = "str";
+    }
+
+    /**
+     * First extended root configuration.
+     */
+    @InternalConfiguration
+    public static class ExtendedFirstRootConfigurationSchema extends FirstRootConfigurationSchema {
+        /** String field. */
+        @Value(hasDefault = true)
+        public String strEx = "str";
+    }
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
index 0d80082..4d2de45 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
@@ -22,12 +22,16 @@ import java.util.Collection;
 import java.util.Map;
 import java.util.Set;
 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.InternalConfiguration;
 import org.apache.ignite.configuration.validation.Validator;
 import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.internal.configuration.tree.InnerNode;
 
 import static java.util.concurrent.CompletableFuture.completedFuture;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.internalSchemaExtensions;
 
 /** Implementation of {@link ConfigurationChanger} to be used in tests. Has no support of listeners. */
 public class TestConfigurationChanger extends ConfigurationChanger {
@@ -41,13 +45,16 @@ public class TestConfigurationChanger extends ConfigurationChanger {
      * @param rootKeys Configuration root keys.
      * @param validators Validators.
      * @param storage Configuration storage.
+     * @param internalSchemaExtensions Internal extensions ({@link InternalConfiguration})
+     *      of configuration schemas ({@link ConfigurationRoot} and {@link Config}).
      * @throws IllegalArgumentException If the configuration type of the root keys is not equal to the storage type.
      */
     public TestConfigurationChanger(
         ConfigurationAsmGenerator cgen,
         Collection<RootKey<?, ?>> rootKeys,
         Map<Class<? extends Annotation>, Set<Validator<?, ?>>> validators,
-        ConfigurationStorage storage
+        ConfigurationStorage storage,
+        Collection<Class<?>> internalSchemaExtensions
     ) {
         super(
             (oldRoot, newRoot, revision) -> completedFuture(null),
@@ -58,7 +65,9 @@ public class TestConfigurationChanger extends ConfigurationChanger {
 
         this.cgen = cgen;
 
-        rootKeys.forEach(key -> cgen.compileRootSchema(key.schemaClass()));
+        Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(internalSchemaExtensions);
+
+        rootKeys.forEach(key -> cgen.compileRootSchema(key.schemaClass(), extensions));
     }
 
     /** {@inheritDoc} */
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGeneratorTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGeneratorTest.java
new file mode 100644
index 0000000..72a5c83
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGeneratorTest.java
@@ -0,0 +1,309 @@
+/*
+ * 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.asm;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.UUID;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.internal.configuration.ConfigurationChanger;
+import org.apache.ignite.internal.configuration.DynamicConfiguration;
+import org.apache.ignite.internal.configuration.TestConfigurationChanger;
+import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
+import org.apache.ignite.internal.configuration.tree.InnerNode;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.addDefaults;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Testing the {@link ConfigurationAsmGenerator}.
+ */
+public class ConfigurationAsmGeneratorTest {
+    /** Configuration changer. */
+    private static ConfigurationChanger changer;
+
+    /** Configuration generator. */
+    private static ConfigurationAsmGenerator generator;
+
+    /** */
+    @BeforeAll
+    public static void beforeAll() {
+        Collection<Class<?>> extensions = List.of(
+            ExtendedTestRootConfigurationSchema.class,
+            ExtendedSecondTestRootConfigurationSchema.class,
+            ExtendedTestConfigurationSchema.class,
+            ExtendedSecondTestConfigurationSchema.class
+        );
+
+        generator = new ConfigurationAsmGenerator();
+
+        changer = new TestConfigurationChanger(
+            generator,
+            List.of(TestRootConfiguration.KEY),
+            Map.of(),
+            new TestConfigurationStorage(LOCAL),
+            extensions
+        );
+
+        changer.start();
+        changer.initializeDefaults();
+    }
+
+    /** */
+    @AfterAll
+    public static void after() {
+        changer.stop();
+
+        generator = null;
+        changer = null;
+    }
+
+    /** */
+    @Test
+    void testExtendedRootConfiguration() throws Exception {
+        DynamicConfiguration<?, ?> config = generator.instantiateCfg(TestRootConfiguration.KEY, changer);
+
+        TestRootConfiguration baseRootConfig = (TestRootConfiguration)config;
+
+        ExtendedTestRootConfiguration extendedRootConfig = (ExtendedTestRootConfiguration)config;
+
+        ExtendedSecondTestRootConfiguration extendedSecondRootConfig = (ExtendedSecondTestRootConfiguration)config;
+
+        assertSame(baseRootConfig.i0(), extendedRootConfig.i0());
+        assertSame(baseRootConfig.i0(), extendedSecondRootConfig.i0());
+
+        assertSame(baseRootConfig.str0(), extendedRootConfig.str0());
+        assertSame(baseRootConfig.str0(), extendedSecondRootConfig.str0());
+
+        assertSame(baseRootConfig.subCfg(), extendedRootConfig.subCfg());
+        assertSame(baseRootConfig.subCfg(), extendedSecondRootConfig.subCfg());
+
+        assertSame(baseRootConfig.namedCfg(), extendedRootConfig.namedCfg());
+        assertSame(baseRootConfig.namedCfg(), extendedSecondRootConfig.namedCfg());
+
+        assertNotNull(extendedSecondRootConfig.i1());
+
+        // Check view and change interfaces.
+
+        assertTrue(baseRootConfig.value() instanceof ExtendedTestRootView);
+        assertTrue(baseRootConfig.value() instanceof ExtendedSecondTestRootView);
+
+        assertSame(baseRootConfig.value(), extendedRootConfig.value());
+        assertSame(baseRootConfig.value(), extendedSecondRootConfig.value());
+
+        baseRootConfig.change(c -> {
+            assertTrue(c instanceof ExtendedTestRootChange);
+            assertTrue(c instanceof ExtendedSecondTestRootChange);
+
+            c.changeI0(10).changeStr0("str0");
+
+            ((ExtendedTestRootChange)c).changeStr1("str1").changeStr0("str0");
+            ((ExtendedSecondTestRootChange)c).changeI1(200).changeStr0("str0");
+        }).get(1, SECONDS);
+    }
+
+    /** */
+    @Test
+    void testExtendedSubConfiguration() throws Exception {
+        DynamicConfiguration<?, ?> config = generator.instantiateCfg(TestRootConfiguration.KEY, changer);
+
+        TestRootConfiguration rootConfig = (TestRootConfiguration)config;
+
+        TestConfiguration baseSubConfig = rootConfig.subCfg();
+
+        ExtendedTestConfiguration extendedSubConfig = (ExtendedTestConfiguration)rootConfig.subCfg();
+
+        ExtendedSecondTestConfiguration extendedSecondSubConfig =
+            (ExtendedSecondTestConfiguration)rootConfig.subCfg();
+
+        assertSame(baseSubConfig.i0(), extendedSubConfig.i0());
+        assertSame(baseSubConfig.i0(), extendedSecondSubConfig.i0());
+
+        assertSame(baseSubConfig.str2(), extendedSubConfig.str2());
+        assertSame(baseSubConfig.str2(), extendedSecondSubConfig.str2());
+
+        assertNotNull(extendedSecondSubConfig.i1());
+
+        // Check view and change interfaces.
+
+        assertTrue(baseSubConfig.value() instanceof ExtendedTestView);
+        assertTrue(baseSubConfig.value() instanceof ExtendedSecondTestView);
+
+        assertSame(baseSubConfig.value(), extendedSubConfig.value());
+        assertSame(baseSubConfig.value(), extendedSecondSubConfig.value());
+
+        baseSubConfig.change(c -> {
+            assertTrue(c instanceof ExtendedTestChange);
+            assertTrue(c instanceof ExtendedSecondTestChange);
+
+            c.changeI0(10).changeStr2("str2");
+
+            ((ExtendedTestChange)c).changeStr3("str3").changeStr2("str2");
+            ((ExtendedSecondTestChange)c).changeI1(200).changeStr2("str2");
+        }).get(1, SECONDS);
+    }
+
+    /** */
+    @Test
+    void testExtendedNamedConfiguration() throws Exception {
+        DynamicConfiguration<?, ?> config = generator.instantiateCfg(TestRootConfiguration.KEY, changer);
+
+        TestRootConfiguration rootConfig = (TestRootConfiguration)config;
+
+        String key = UUID.randomUUID().toString();
+
+        rootConfig.namedCfg().change(c -> c.create(key, c0 -> c0.changeI0(0).changeStr2("foo2"))).get(1, SECONDS);
+
+        TestConfiguration namedConfig = rootConfig.namedCfg().get(key);
+
+        assertTrue(namedConfig instanceof ExtendedTestConfiguration);
+        assertTrue(namedConfig instanceof ExtendedSecondTestConfiguration);
+
+        // Check view and change interfaces.
+
+        assertTrue(namedConfig.value() instanceof ExtendedTestView);
+        assertTrue(namedConfig.value() instanceof ExtendedSecondTestView);
+
+        namedConfig.change(c -> {
+            assertTrue(c instanceof ExtendedTestChange);
+            assertTrue(c instanceof ExtendedSecondTestChange);
+
+            c.changeStr2("str2").changeI0(10);
+
+            ((ExtendedTestChange)c).changeStr3("str3").changeStr2("str2");
+            ((ExtendedSecondTestChange)c).changeI1(100).changeStr2("str2");
+        }).get(1, SECONDS);
+    }
+
+    /** */
+    @Test
+    void testConstructInternalConfig() {
+        InnerNode innerNode = generator.instantiateNode(TestRootConfiguration.KEY.schemaClass());
+
+        addDefaults(innerNode);
+
+        InnerNode subInnerNode = (InnerNode)((TestRootView)innerNode).subCfg();
+
+        // Check that no fields for internal configuration will be changed.
+
+        assertThrows(NoSuchElementException.class, () -> innerNode.construct("str1", null, false));
+        assertThrows(NoSuchElementException.class, () -> innerNode.construct("i1", null, false));
+
+        assertThrows(NoSuchElementException.class, () -> subInnerNode.construct("str3", null, false));
+        assertThrows(NoSuchElementException.class, () -> subInnerNode.construct("i1", null, false));
+
+        // Check that fields for internal configuration will be changed.
+
+        innerNode.construct("str1", null, true);
+        innerNode.construct("i1", null, true);
+
+        subInnerNode.construct("str3", null, true);
+        subInnerNode.construct("i1", null, true);
+    }
+
+    /**
+     * Test root configuration schema.
+     */
+    @ConfigurationRoot(rootName = "test")
+    public static class TestRootConfigurationSchema {
+        /** Integer field. */
+        @Value(hasDefault = true)
+        public int i0 = 0;
+
+        /** String field. */
+        @Value(hasDefault = true)
+        public String str0 = "str0";
+
+        /** Sub configuration field. */
+        @ConfigValue
+        public TestConfigurationSchema subCfg;
+
+        /** Named configuration field. */
+        @NamedConfigValue
+        public TestConfigurationSchema namedCfg;
+    }
+
+    /**
+     * Extending the {@link TestRootConfigurationSchema}.
+     */
+    @InternalConfiguration
+    public static class ExtendedTestRootConfigurationSchema extends TestRootConfigurationSchema {
+        /** String field. */
+        @Value(hasDefault = true)
+        public String str1 = "str1";
+    }
+
+    /**
+     * Extending the {@link TestRootConfigurationSchema}.
+     */
+    @InternalConfiguration
+    public static class ExtendedSecondTestRootConfigurationSchema extends TestRootConfigurationSchema {
+        /** Integer field. */
+        @Value(hasDefault = true)
+        public int i1 = 0;
+    }
+
+    /**
+     * Test configuration schema.
+     */
+    @Config
+    public static class TestConfigurationSchema {
+        /** Integer field. */
+        @Value(hasDefault = true)
+        public int i0 = 0;
+
+        /** String field. */
+        @Value(hasDefault = true)
+        public String str2 = "str2";
+    }
+
+    /**
+     * Extending the {@link TestConfigurationSchema}.
+     */
+    @InternalConfiguration
+    public static class ExtendedTestConfigurationSchema extends TestConfigurationSchema {
+        /** String field. */
+        @Value(hasDefault = true)
+        public String str3 = "str3";
+    }
+
+    /**
+     * Extending the {@link TestConfigurationSchema}.
+     */
+    @InternalConfiguration
+    public static class ExtendedSecondTestConfigurationSchema extends TestConfigurationSchema {
+        /** Integer field. */
+        @Value(hasDefault = true)
+        public int i1 = 0;
+    }
+}
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
index 85174e8..4adb616 100644
--- 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
@@ -160,7 +160,8 @@ public class HoconConverterTest {
         registry = new ConfigurationRegistry(
             List.of(HoconRootConfiguration.KEY),
             Map.of(),
-            new TestConfigurationStorage(LOCAL)
+            new TestConfigurationStorage(LOCAL),
+            List.of()
         );
 
         registry.start();
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
index 77753dd..72e312c 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
@@ -78,7 +78,12 @@ public class ConfigurationListenerTest {
     public void before() {
         var testConfigurationStorage = new TestConfigurationStorage(LOCAL);
 
-        registry = new ConfigurationRegistry(List.of(ParentConfiguration.KEY), Map.of(), testConfigurationStorage);
+        registry = new ConfigurationRegistry(
+            List.of(ParentConfiguration.KEY),
+            Map.of(),
+            testConfigurationStorage,
+            List.of()
+        );
 
         registry.start();
 
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java
index 72a8f0a..c747ee5 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java
@@ -52,7 +52,8 @@ public class UsageTest {
         registry = new ConfigurationRegistry(
             List.of(LocalConfiguration.KEY),
             Map.of(),
-            new TestConfigurationStorage(LOCAL)
+            new TestConfigurationStorage(LOCAL),
+            List.of()
         );
 
         registry.start();
@@ -112,7 +113,8 @@ public class UsageTest {
         registry = new ConfigurationRegistry(
             List.of(NetworkConfiguration.KEY, LocalConfiguration.KEY),
             Map.of(),
-            new TestConfigurationStorage(LOCAL)
+            new TestConfigurationStorage(LOCAL),
+            List.of()
         );
 
         registry.start();
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/ConfigurationArrayTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/ConfigurationArrayTest.java
index a0422ff..927e047 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/ConfigurationArrayTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/ConfigurationArrayTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.configuration.tree;
 
+import java.util.Map;
 import java.util.Random;
 import java.util.UUID;
 import java.util.stream.Stream;
@@ -84,7 +85,7 @@ public class ConfigurationArrayTest {
     /** */
     @BeforeAll
     public static void beforeAll() {
-        cgen.compileRootSchema(TestArrayConfigurationSchema.class);
+        cgen.compileRootSchema(TestArrayConfigurationSchema.class, Map.of());
     }
 
     /** */
@@ -145,7 +146,7 @@ public class ConfigurationArrayTest {
      * Gets an array field from the given {@code InnerNode}.
      */
     private static <T> T getArray(InnerNode arrayNode, Class<T> cls) {
-        return cls.cast(arrayNode.traverseChild(getFieldName(cls), leafNodeVisitor()));
+        return cls.cast(arrayNode.traverseChild(getFieldName(cls), leafNodeVisitor(), true));
     }
 
     /**
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java
index 6d1503c..4d72612 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.configuration.tree;
 
 import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
 import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
 import org.junit.jupiter.api.AfterAll;
@@ -38,7 +39,7 @@ public class ConstructableTreeNodeTest {
     public static void beforeAll() {
         cgen = new ConfigurationAsmGenerator();
 
-        cgen.compileRootSchema(TraversableTreeNodeTest.ParentConfigurationSchema.class);
+        cgen.compileRootSchema(TraversableTreeNodeTest.ParentConfigurationSchema.class, Map.of());
     }
 
     @AfterAll
@@ -59,7 +60,7 @@ public class ConstructableTreeNodeTest {
     public void noKey() {
         var childNode = newChildInstance();
 
-        assertThrows(NoSuchElementException.class, () -> childNode.construct("foo", null));
+        assertThrows(NoSuchElementException.class, () -> childNode.construct("foo", null, true));
     }
 
     /** */
@@ -77,7 +78,7 @@ public class ConstructableTreeNodeTest {
         // Named list node.
         var elements = parentNode.elements();
 
-        parentNode.construct("elements", null);
+        parentNode.construct("elements", null, true);
 
         assertNotNull(parentNode.elements());
         assertNotSame(elements, parentNode.elements());
@@ -86,12 +87,12 @@ public class ConstructableTreeNodeTest {
         // Inner node.
         NamedElementView element = elements.get("name");
 
-        ((ConstructableTreeNode)elements).construct("name", null);
+        ((ConstructableTreeNode)elements).construct("name", null, true);
 
         assertNull(elements.get("name"));
 
         // Leaf.
-        ((ConstructableTreeNode)element).construct("strCfg", null);
+        ((ConstructableTreeNode)element).construct("strCfg", null, true);
 
         assertNull(element.strCfg());
     }
@@ -119,16 +120,16 @@ public class ConstructableTreeNodeTest {
     public void unwrap() {
         var childNode = newChildInstance();
 
-        childNode.construct("strCfg", new ConstantConfigurationSource("value"));
+        childNode.construct("strCfg", new ConstantConfigurationSource("value"), true);
 
         assertEquals("value", childNode.strCfg());
 
-        childNode.construct("intCfg", new ConstantConfigurationSource(255));
+        childNode.construct("intCfg", new ConstantConfigurationSource(255), true);
 
         assertEquals(255, childNode.intCfg());
 
         assertThrows(ClassCastException.class, () ->
-            childNode.construct("intCfg", new ConstantConfigurationSource(new Object()))
+            childNode.construct("intCfg", new ConstantConfigurationSource(new Object()), true)
         );
     }
 
@@ -144,9 +145,9 @@ public class ConstructableTreeNodeTest {
             }
 
             @Override public void descend(ConstructableTreeNode node) {
-                node.construct("strCfg", new ConstantConfigurationSource("value"));
+                node.construct("strCfg", new ConstantConfigurationSource("value"), true);
             }
-        });
+        }, true);
 
         assertEquals("value", parentNode.child().strCfg());
 
@@ -159,9 +160,9 @@ public class ConstructableTreeNodeTest {
             }
 
             @Override public void descend(ConstructableTreeNode node) {
-                node.construct("strCfg", new ConstantConfigurationSource("value"));
+                node.construct("strCfg", new ConstantConfigurationSource("value"), true);
             }
-        });
+        }, true);
 
         assertEquals("value", elementsNode.get("name").strCfg());
     }
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
index e1b1491..eb28d04 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
@@ -90,7 +90,7 @@ public class NamedListNodeTest {
     public void before() {
         storage = new TestConfigurationStorage(LOCAL);
 
-        changer = new TestConfigurationChanger(cgen, List.of(AConfiguration.KEY), Map.of(), storage);
+        changer = new TestConfigurationChanger(cgen, List.of(AConfiguration.KEY), Map.of(), storage, List.of());
         changer.start();
     }
 
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 d2f9b52..f6ddfd0 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
@@ -18,9 +18,11 @@
 package org.apache.ignite.internal.configuration.tree;
 
 import java.io.Serializable;
-import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.TreeSet;
 import org.apache.ignite.configuration.NamedListChange;
 import org.apache.ignite.configuration.NamedListView;
 import org.apache.ignite.configuration.annotation.Config;
@@ -52,7 +54,7 @@ public class TraversableTreeNodeTest {
     public static void beforeAll() {
         cgen = new ConfigurationAsmGenerator();
 
-        cgen.compileRootSchema(ParentConfigurationSchema.class);
+        cgen.compileRootSchema(ParentConfigurationSchema.class, Map.of());
     }
 
     @AfterAll
@@ -277,7 +279,7 @@ public class TraversableTreeNodeTest {
     public void traverseChildren() {
         var parentNode = newParentInstance();
 
-        List<String> keys = new ArrayList<>(2);
+        Collection<String> keys = new TreeSet<>();
 
         parentNode.traverseChildren(new ConfigurationVisitor<Object>() {
             @Override public Object visitInnerNode(String key, InnerNode node) {
@@ -293,10 +295,10 @@ public class TraversableTreeNodeTest {
 
                 return keys.add(key);
             }
-        });
+        }, true);
 
         // Assert that updates happened in the same order as fields declaration in schema.
-        assertEquals(List.of("child", "elements"), keys);
+        assertEquals(new TreeSet<>(List.of("child", "elements")), keys);
 
         keys.clear();
 
@@ -306,10 +308,10 @@ public class TraversableTreeNodeTest {
             @Override public Object visitLeafNode(String key, Serializable val) {
                 return keys.add(key);
             }
-        });
+        }, true);
 
         // Assert that updates happened in the same order as fields declaration in schema.
-        assertEquals(List.of("intCfg", "strCfg"), keys);
+        assertEquals(new TreeSet<>(List.of("intCfg", "strCfg")), keys);
     }
 
     /**
@@ -327,7 +329,7 @@ public class TraversableTreeNodeTest {
 
                     throw new VisitException();
                 }
-            })
+            }, true)
         );
 
         // Assert that proper method has been invoked.
@@ -339,7 +341,7 @@ public class TraversableTreeNodeTest {
 
                     throw new VisitException();
                 }
-            })
+            }, true)
         );
 
         var childNode = newChildInstance();
@@ -352,12 +354,12 @@ public class TraversableTreeNodeTest {
 
                     throw new VisitException();
                 }
-            })
+            }, true)
         );
 
         // Assert that traversing inexistent field leads to exception.
         assertThrows(NoSuchElementException.class, () ->
-            childNode.traverseChild("foo", new ConfigurationVisitor<>() {})
+            childNode.traverseChild("foo", new ConfigurationVisitor<>() {}, true)
         );
     }
 }
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
index fa72dff..0f61d65 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
@@ -18,19 +18,26 @@
 package org.apache.ignite.internal.configuration.util;
 
 import java.io.Serializable;
+import java.lang.reflect.Field;
 import java.util.List;
 import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
 import java.util.function.Consumer;
+import java.util.stream.Stream;
 import org.apache.ignite.configuration.RootKey;
 import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.ConfigValue;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.internal.configuration.RootInnerNode;
 import org.apache.ignite.internal.configuration.SuperRoot;
 import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
 import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
 import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
+import org.apache.ignite.internal.configuration.tree.ConverterToMapVisitor;
 import org.apache.ignite.internal.configuration.tree.InnerNode;
 import org.apache.ignite.internal.configuration.tree.TraversableTreeNode;
 import org.jetbrains.annotations.NotNull;
@@ -39,12 +46,18 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 import static java.util.Collections.singletonMap;
+import static java.util.stream.Collectors.toSet;
 import static org.apache.ignite.configuration.annotation.ConfigurationType.DISTRIBUTED;
 import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
 import static org.apache.ignite.internal.configuration.tree.NamedListNode.NAME;
 import static org.apache.ignite.internal.configuration.tree.NamedListNode.ORDER_IDX;
 import static org.apache.ignite.internal.configuration.util.ConfigurationFlattener.createFlattenedUpdatesMap;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.addDefaults;
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.checkConfigurationType;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.collectSchemas;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.extensionsFields;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.find;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.internalSchemaExtensions;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.aMapWithSize;
 import static org.hamcrest.Matchers.allOf;
@@ -55,9 +68,11 @@ import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.matchesPattern;
 import static org.hamcrest.Matchers.nullValue;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /** */
 public class ConfigurationUtilTest {
@@ -67,7 +82,7 @@ public class ConfigurationUtilTest {
     public static void beforeAll() {
         cgen = new ConfigurationAsmGenerator();
 
-        cgen.compileRootSchema(ParentConfigurationSchema.class);
+        cgen.compileRootSchema(ParentConfigurationSchema.class, Map.of());
     }
 
     @AfterAll
@@ -140,8 +155,8 @@ public class ConfigurationUtilTest {
     }
 
     /**
-     * Tests that {@link ConfigurationUtil#find(List, TraversableTreeNode)} finds proper node when provided with correct
-     * path.
+     * Tests that {@link ConfigurationUtil#find(List, TraversableTreeNode, boolean)} finds proper node when provided
+     * with correct path.
      */
     @Test
     public void findSuccessfully() {
@@ -157,52 +172,52 @@ public class ConfigurationUtilTest {
 
         assertSame(
             parent,
-            ConfigurationUtil.find(List.of(), parent)
+            ConfigurationUtil.find(List.of(), parent, true)
         );
 
         assertSame(
             parent.elements(),
-            ConfigurationUtil.find(List.of("elements"), parent)
+            ConfigurationUtil.find(List.of("elements"), parent, true)
         );
 
         assertSame(
             parent.elements().get("name"),
-            ConfigurationUtil.find(List.of("elements", "name"), parent)
+            ConfigurationUtil.find(List.of("elements", "name"), parent, true)
         );
 
         assertSame(
             parent.elements().get("name").child(),
-            ConfigurationUtil.find(List.of("elements", "name", "child"), parent)
+            ConfigurationUtil.find(List.of("elements", "name", "child"), parent, true)
         );
 
         assertSame(
             parent.elements().get("name").child().str(),
-            ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parent)
+            ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parent, true)
         );
     }
 
     /**
-     * Tests that {@link ConfigurationUtil#find(List, TraversableTreeNode)} returns null when path points to nonexistent
-     * named list element.
+     * Tests that {@link ConfigurationUtil#find(List, TraversableTreeNode, boolean)} returns null when path points to
+     * nonexistent named list element.
      */
     @Test
     public void findNulls() {
         var parent = newParentInstance();
 
-        assertNull(ConfigurationUtil.find(List.of("elements", "name"), parent));
+        assertNull(ConfigurationUtil.find(List.of("elements", "name"), parent, true));
 
         parent.changeElements(elements -> elements.createOrUpdate("name", element -> {}));
 
-        assertNull(ConfigurationUtil.find(List.of("elements", "name", "child"), parent));
+        assertNull(ConfigurationUtil.find(List.of("elements", "name", "child"), parent, true));
 
         ((NamedElementChange)parent.elements().get("name")).changeChild(child -> {});
 
-        assertNull(ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parent));
+        assertNull(ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parent, true));
     }
 
     /**
-     * Tests that {@link ConfigurationUtil#find(List, TraversableTreeNode)} throws {@link KeyNotFoundException} when
-     * provided with a wrong path.
+     * Tests that {@link ConfigurationUtil#find(List, TraversableTreeNode, boolean)} throws
+     * {@link KeyNotFoundException} when provided with a wrong path.
      */
     @Test
     public void findUnsuccessfully() {
@@ -210,21 +225,21 @@ public class ConfigurationUtilTest {
 
         assertThrows(
             KeyNotFoundException.class,
-            () -> ConfigurationUtil.find(List.of("elements", "name", "child"), parent)
+            () -> ConfigurationUtil.find(List.of("elements", "name", "child"), parent, true)
         );
 
         parent.changeElements(elements -> elements.createOrUpdate("name", element -> {}));
 
         assertThrows(
             KeyNotFoundException.class,
-            () -> ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parent)
+            () -> ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parent, true)
         );
 
         ((NamedElementChange)parent.elements().get("name")).changeChild(child -> child.changeStr("value"));
 
         assertThrows(
             KeyNotFoundException.class,
-            () -> ConfigurationUtil.find(List.of("elements", "name", "child", "str", "foo"), parent)
+            () -> ConfigurationUtil.find(List.of("elements", "name", "child", "str", "foo"), parent, true)
         );
     }
 
@@ -395,6 +410,225 @@ public class ConfigurationUtilTest {
         );
     }
 
+    /** */
+    @Test
+    void testInternalSchemaExtensions() {
+        assertTrue(internalSchemaExtensions(List.of()).isEmpty());
+
+        assertThrows(IllegalArgumentException.class, () -> internalSchemaExtensions(List.of(Object.class)));
+
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> internalSchemaExtensions(List.of(SimpleRootConfigurationSchema.class))
+        );
+
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> internalSchemaExtensions(List.of(SimpleConfigurationSchema.class))
+        );
+
+        Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(List.of(
+            InternalFirstSimpleRootConfigurationSchema.class,
+            InternalSecondSimpleRootConfigurationSchema.class,
+            InternalFirstSimpleConfigurationSchema.class,
+            InternalSecondSimpleConfigurationSchema.class
+        ));
+
+        assertEquals(2, extensions.size());
+
+        assertEquals(
+            Set.of(
+                InternalFirstSimpleRootConfigurationSchema.class,
+                InternalSecondSimpleRootConfigurationSchema.class
+            ),
+            extensions.get(SimpleRootConfigurationSchema.class)
+        );
+
+        assertEquals(
+            Set.of(
+                InternalFirstSimpleConfigurationSchema.class,
+                InternalSecondSimpleConfigurationSchema.class
+            ),
+            extensions.get(SimpleConfigurationSchema.class)
+        );
+    }
+
+    /** */
+    @Test
+    void testSchemaFields() {
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> extensionsFields(
+                List.of(
+                    InternalExtendedSimpleRootConfigurationSchema.class,
+                    ErrorInternalExtendedSimpleRootConfigurationSchema.class
+                )
+            )
+        );
+
+        assertTrue(extensionsFields(List.of()).isEmpty());
+
+        List<Class<?>> extensions = List.of(
+            InternalFirstSimpleRootConfigurationSchema.class,
+            InternalSecondSimpleRootConfigurationSchema.class
+        );
+
+        Set<Field> exp = extensions.stream()
+            .flatMap(cls -> Stream.of(cls.getDeclaredFields()))
+            .collect(toSet());
+
+        assertEquals(exp, extensionsFields(extensions));
+    }
+
+    /** */
+    @Test
+    void testFindInternalConfigs() {
+        Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(List.of(
+            InternalFirstSimpleRootConfigurationSchema.class,
+            InternalSecondSimpleRootConfigurationSchema.class,
+            InternalFirstSimpleConfigurationSchema.class,
+            InternalSecondSimpleConfigurationSchema.class
+        ));
+
+        ConfigurationAsmGenerator generator = new ConfigurationAsmGenerator();
+        generator.compileRootSchema(SimpleRootConfigurationSchema.class, extensions);
+
+        InnerNode innerNode = generator.instantiateNode(SimpleRootConfigurationSchema.class);
+
+        addDefaults(innerNode);
+
+        // Check that no internal configuration will be found.
+
+        assertThrows(KeyNotFoundException.class, () -> find(List.of("str2"), innerNode, false));
+        assertThrows(KeyNotFoundException.class, () -> find(List.of("str3"), innerNode, false));
+        assertThrows(KeyNotFoundException.class, () -> find(List.of("subCfg1"), innerNode, false));
+
+        assertThrows(KeyNotFoundException.class, () -> find(List.of("subCfg", "str01"), innerNode, false));
+        assertThrows(KeyNotFoundException.class, () -> find(List.of("subCfg", "str02"), innerNode, false));
+
+        // Check that internal configuration will be found.
+
+        assertNull(find(List.of("str2"), innerNode, true));
+        assertEquals("foo", find(List.of("str3"), innerNode, true));
+        assertNotNull(find(List.of("subCfg1"), innerNode, true));
+
+        assertEquals("foo", find(List.of("subCfg", "str01"), innerNode, true));
+        assertEquals("foo", find(List.of("subCfg", "str02"), innerNode, true));
+    }
+
+    /** */
+    @Test
+    void testGetInternalConfigs() {
+        Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(List.of(
+            InternalFirstSimpleRootConfigurationSchema.class,
+            InternalSecondSimpleRootConfigurationSchema.class,
+            InternalFirstSimpleConfigurationSchema.class,
+            InternalSecondSimpleConfigurationSchema.class
+        ));
+
+        ConfigurationAsmGenerator generator = new ConfigurationAsmGenerator();
+        generator.compileRootSchema(SimpleRootConfigurationSchema.class, extensions);
+
+        InnerNode innerNode = generator.instantiateNode(SimpleRootConfigurationSchema.class);
+
+        addDefaults(innerNode);
+
+        Map<String, Object> config = (Map<String, Object>)innerNode.accept(null, new ConverterToMapVisitor(false));
+
+        // Check that no internal configuration will be received.
+
+        assertEquals(4, config.size());
+        assertNull(config.get("str0"));
+        assertEquals("foo", config.get("str1"));
+        assertNotNull(config.get("subCfg"));
+        assertNotNull(config.get("namedCfg"));
+
+        Map<String, Object> subConfig = (Map<String, Object>)config.get("subCfg");
+
+        assertEquals(1, subConfig.size());
+        assertEquals("foo", subConfig.get("str00"));
+
+        // Check that no internal configuration will be received.
+
+        config = (Map<String, Object>)innerNode.accept(null, new ConverterToMapVisitor(true));
+
+        assertEquals(7, config.size());
+        assertNull(config.get("str0"));
+        assertNull(config.get("str2"));
+        assertEquals("foo", config.get("str1"));
+        assertEquals("foo", config.get("str3"));
+        assertNotNull(config.get("subCfg"));
+        assertNotNull(config.get("subCfg1"));
+        assertNotNull(config.get("namedCfg"));
+
+        subConfig = (Map<String, Object>)config.get("subCfg");
+
+        assertEquals(3, subConfig.size());
+        assertEquals("foo", subConfig.get("str00"));
+        assertEquals("foo", subConfig.get("str01"));
+        assertEquals("foo", subConfig.get("str02"));
+
+        subConfig = (Map<String, Object>)config.get("subCfg1");
+
+        assertEquals(3, subConfig.size());
+        assertEquals("foo", subConfig.get("str00"));
+        assertEquals("foo", subConfig.get("str01"));
+        assertEquals("foo", subConfig.get("str02"));
+    }
+
+    /** */
+    @Test
+    void testSuperRootWithInternalConfig() {
+        ConfigurationAsmGenerator generator = new ConfigurationAsmGenerator();
+
+        Class<?> schemaClass = InternalWithoutSuperclassConfigurationSchema.class;
+        RootKey<?, ?> schemaKey = InternalWithoutSuperclassConfiguration.KEY;
+
+        generator.compileRootSchema(schemaClass, Map.of());
+
+        SuperRoot superRoot = new SuperRoot(
+            s -> new RootInnerNode(schemaKey, generator.instantiateNode(schemaClass))
+        );
+
+        assertThrows(NoSuchElementException.class, () -> superRoot.construct(schemaKey.key(), null, false));
+
+        superRoot.construct(schemaKey.key(), null, true);
+
+        superRoot.addRoot(schemaKey, generator.instantiateNode(schemaClass));
+
+        assertThrows(KeyNotFoundException.class, () -> find(List.of(schemaKey.key()), superRoot, false));
+
+        assertNotNull(find(List.of(schemaKey.key()), superRoot, true));
+
+        Map<String, Object> config =
+            (Map<String, Object>)superRoot.accept(schemaKey.key(), new ConverterToMapVisitor(false));
+
+        assertTrue(config.isEmpty());
+
+        config = (Map<String, Object>)superRoot.accept(schemaKey.key(), new ConverterToMapVisitor(true));
+
+        assertEquals(1, config.size());
+        assertNotNull(config.get(schemaKey.key()));
+    }
+
+    /** */
+    @Test
+    void testCollectSchemas() {
+        assertTrue(collectSchemas(List.of()).isEmpty());
+
+        assertThrows(IllegalArgumentException.class, () -> collectSchemas(List.of(Object.class)));
+
+        assertEquals(
+            Set.of(LocalFirstConfigurationSchema.class, SimpleConfigurationSchema.class),
+            collectSchemas(List.of(LocalFirstConfigurationSchema.class, SimpleConfigurationSchema.class))
+        );
+
+        assertEquals(
+            Set.of(SimpleRootConfigurationSchema.class, SimpleConfigurationSchema.class),
+            collectSchemas(List.of(SimpleRootConfigurationSchema.class))
+        );
+    }
+
     /**
      * Patches super root and returns flat representation of the changes. Passed {@code superRoot} object will contain
      * patched tree when method execution is completed.
@@ -408,7 +642,7 @@ public class ConfigurationUtilTest {
         SuperRoot originalSuperRoot = superRoot.copy();
 
         // Make a copy of the root insode of the superRoot. This copy will be used for further patching.
-        superRoot.construct(ParentConfiguration.KEY.key(), new ConfigurationSource() {});
+        superRoot.construct(ParentConfiguration.KEY.key(), new ConfigurationSource() {}, true);
 
         // Patch root node.
         patch.accept((ParentChange)superRoot.getRoot(ParentConfiguration.KEY));
@@ -456,4 +690,120 @@ public class ConfigurationUtilTest {
         @Value(hasDefault = true)
         public String str = "str";
     }
+
+    /**
+     * Simple root configuration schema.
+     */
+    @ConfigurationRoot(rootName = "testRootSimple")
+    public static class SimpleRootConfigurationSchema {
+        /** String value without default. */
+        @Value
+        public String str0;
+
+        /** String value with default. */
+        @Value(hasDefault = true)
+        public String str1 = "foo";
+
+        /** Sub configuration schema. */
+        @ConfigValue
+        public SimpleConfigurationSchema subCfg;
+
+        /** Named configuration schema. */
+        @NamedConfigValue
+        public SimpleConfigurationSchema namedCfg;
+    }
+
+    /**
+     * Simple configuration schema.
+     */
+    @Config
+    public static class SimpleConfigurationSchema {
+        /** String value with default. */
+        @Value(hasDefault = true)
+        public String str00 = "foo";
+    }
+
+    /**
+     * Internal schema extension without superclass.
+     */
+    @InternalConfiguration
+    @ConfigurationRoot(rootName = "testRootInternal")
+    public static class InternalWithoutSuperclassConfigurationSchema {
+    }
+
+    /**
+     * First simple internal schema extension.
+     */
+    @InternalConfiguration
+    public static class InternalFirstSimpleConfigurationSchema extends SimpleConfigurationSchema {
+        /** String value with default. */
+        @Value(hasDefault = true)
+        public String str01 = "foo";
+    }
+
+    /**
+     * Second simple internal schema extension.
+     */
+    @InternalConfiguration
+    public static class InternalSecondSimpleConfigurationSchema extends SimpleConfigurationSchema {
+        /** String value with default. */
+        @Value(hasDefault = true)
+        public String str02 = "foo";
+    }
+
+    /**
+     * First root simple internal schema extension.
+     */
+    @InternalConfiguration
+    public static class InternalFirstSimpleRootConfigurationSchema extends SimpleRootConfigurationSchema {
+        /** Second string value without default. */
+        @Value
+        public String str2;
+
+        /** Second string value with default. */
+        @Value(hasDefault = true)
+        public String str3 = "foo";
+    }
+
+    /**
+     * Second root simple internal schema extension.
+     */
+    @InternalConfiguration
+    public static class InternalSecondSimpleRootConfigurationSchema extends SimpleRootConfigurationSchema {
+        /** Second sub configuration schema. */
+        @ConfigValue
+        public SimpleConfigurationSchema subCfg1;
+    }
+
+    /**
+     * Internal extended simple root configuration schema.
+     */
+    @InternalConfiguration
+    public static class InternalExtendedSimpleRootConfigurationSchema extends SimpleRootConfigurationSchema {
+        /** String value without default. */
+        @Value
+        public String str00;
+
+        /** String value with default. */
+        @Value(hasDefault = true)
+        public String str01 = "foo";
+
+        /** Sub configuration schema. */
+        @ConfigValue
+        public SimpleConfigurationSchema subCfg0;
+
+        /** Named configuration schema. */
+        @NamedConfigValue
+        public SimpleConfigurationSchema namedCfg0;
+    }
+
+    /**
+     * Error: Duplicate field.
+     */
+    @InternalConfiguration
+    public static class ErrorInternalExtendedSimpleRootConfigurationSchema extends SimpleRootConfigurationSchema {
+        /** String value without default. */
+        @Value
+        public String str00;
+    }
 }
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java
index 8946f09..e394fe3 100644
--- a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java
@@ -55,7 +55,7 @@ public class ValidationUtilTest {
     public static void beforeAll() {
         cgen = new ConfigurationAsmGenerator();
 
-        cgen.compileRootSchema(ValidatedRootConfigurationSchema.class);
+        cgen.compileRootSchema(ValidatedRootConfigurationSchema.class, Map.of());
     }
 
     @AfterAll
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/RootKey.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/RootKey.java
index 3ff15be..af08226 100644
--- a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/RootKey.java
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/RootKey.java
@@ -19,6 +19,7 @@ package org.apache.ignite.configuration;
 
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.annotation.ConfigurationType;
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
 import org.apache.ignite.internal.tostring.S;
 
 /**
@@ -37,6 +38,9 @@ public class RootKey<T extends ConfigurationTree<VIEW, ?>, VIEW> {
     /** Schema class for the root. */
     private final Class<?> schemaClass;
 
+    /** Marked with {@link InternalConfiguration}. */
+    private final boolean internal;
+
     /**
      * Constructor.
      *
@@ -51,6 +55,8 @@ public class RootKey<T extends ConfigurationTree<VIEW, ?>, VIEW> {
 
         this.rootName = rootAnnotation.rootName();
         this.storageType = rootAnnotation.type();
+
+        internal = schemaClass.isAnnotationPresent(InternalConfiguration.class);
     }
 
     /**
@@ -80,6 +86,15 @@ public class RootKey<T extends ConfigurationTree<VIEW, ?>, VIEW> {
         return schemaClass;
     }
 
+    /**
+     * Check if the root configuration is marked with {@link InternalConfiguration}.
+     *
+     * @return {@code true} if the root configuration is internal.
+     */
+    public boolean internal() {
+        return internal;
+    }
+
     /** {@inheritDoc} */
     @Override public String toString() {
         return S.toString(RootKey.class, this);
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/InternalConfiguration.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/InternalConfiguration.java
new file mode 100644
index 0000000..f0ed596
--- /dev/null
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/InternalConfiguration.java
@@ -0,0 +1,42 @@
+/*
+ * 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 java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * This annotation can only be applied to a class that is either marked with {@link ConfigurationRoot}
+ * or the superclass is marked with {@link ConfigurationRoot}, {@link Config}.
+ * <p>
+ * It indicates that this is an internal configuration that should be hidden from the end user.
+ * Any extensions are allowed for any configuration.
+ * <p>
+ * NOTE: Field name collisions are not allowed.
+ *
+ * @see ConfigurationRoot
+ * @see Config
+ */
+@Target(TYPE)
+@Retention(RUNTIME)
+@Documented
+public @interface InternalConfiguration {
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
index b64f291..ff48983 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
@@ -158,11 +158,11 @@ public abstract class ConfigurationChanger {
      *
      * @return Function that creates root node by root name or returns {@code null} if root name is not found.
      */
-    private Function<String, InnerNode> rootCreator() {
+    private Function<String, RootInnerNode> rootCreator() {
         return key -> {
             RootKey<?, ?> rootKey = rootKeys.get(key);
 
-            return rootKey == null ? null : createRootNode(rootKey);
+            return rootKey == null ? null : new RootInnerNode(rootKey, createRootNode(rootKey));
         };
     }
 
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 c1c6056..170594d 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
@@ -25,6 +25,9 @@ import java.util.concurrent.ExecutionException;
 import com.typesafe.config.ConfigFactory;
 import com.typesafe.config.ConfigObject;
 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.InternalConfiguration;
 import org.apache.ignite.configuration.validation.Validator;
 import org.apache.ignite.internal.configuration.hocon.HoconConverter;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
@@ -45,16 +48,20 @@ public class ConfigurationManager implements IgniteComponent {
      * @param rootKeys Configuration root keys.
      * @param validators Validators.
      * @param storage Configuration storage.
-     * @throws IllegalArgumentException If the configuration type of the root keys is not equal to the storage type.
+     * @param internalSchemaExtensions Internal extensions ({@link InternalConfiguration})
+     *      of configuration schemas ({@link ConfigurationRoot} and {@link Config}).
+     * @throws IllegalArgumentException If the configuration type of the root keys is not equal to the storage type,
+     *      or if the schema or its extensions are not valid.
      */
     public ConfigurationManager(
         Collection<RootKey<?, ?>> rootKeys,
         Map<Class<? extends Annotation>, Set<Validator<? extends Annotation, ?>>> validators,
-        ConfigurationStorage storage
+        ConfigurationStorage storage,
+        Collection<Class<?>> internalSchemaExtensions
     ) {
         checkConfigurationType(rootKeys, storage);
 
-        registry = new ConfigurationRegistry(rootKeys, validators, storage);
+        registry = new ConfigurationRegistry(rootKeys, validators, storage, internalSchemaExtensions);
     }
 
     /** {@inheritDoc} */
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationNode.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationNode.java
index a7a6807..8025153 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationNode.java
@@ -106,7 +106,7 @@ public abstract class ConfigurationNode<VIEW, CHANGE> implements ConfigurationPr
             return val;
 
         try {
-            VIEW newVal = (VIEW)ConfigurationUtil.find(keys.subList(1, keys.size()), newRootNode);
+            VIEW newVal = (VIEW)ConfigurationUtil.find(keys.subList(1, keys.size()), newRootNode, true);
 
             synchronized (this) {
                 if (cachedRootNode == oldRootNode) {
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java
index 8ef663e..09223bf 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java
@@ -30,6 +30,9 @@ import java.util.concurrent.CompletableFuture;
 import java.util.function.Function;
 import org.apache.ignite.configuration.ConfigurationTree;
 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.InternalConfiguration;
 import org.apache.ignite.configuration.validation.Immutable;
 import org.apache.ignite.configuration.validation.Max;
 import org.apache.ignite.configuration.validation.Min;
@@ -49,16 +52,20 @@ import org.apache.ignite.internal.configuration.validation.MinValidator;
 import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.lang.IgniteLogger;
 
+import static java.util.function.Predicate.not;
+import static java.util.stream.Collectors.toSet;
 import static org.apache.ignite.internal.configuration.util.ConfigurationNotificationsUtil.notifyListeners;
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.checkConfigurationType;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.collectSchemas;
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.innerNodeVisitor;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.internalSchemaExtensions;
 
 /** */
 public class ConfigurationRegistry implements IgniteComponent {
     /** The logger. */
     private static final IgniteLogger LOG = IgniteLogger.forClass(ConfigurationRegistry.class);
 
-    /** */
+    /** Generated configuration implementations. Mapping: {@link RootKey#key} -> configuration implementation. */
     private final Map<String, DynamicConfiguration<?, ?>> configs = new HashMap<>();
 
     /** Root keys. */
@@ -76,15 +83,33 @@ public class ConfigurationRegistry implements IgniteComponent {
      * @param rootKeys Configuration root keys.
      * @param validators Validators.
      * @param storage Configuration storage.
-     * @throws IllegalArgumentException If the configuration type of the root keys is not equal to the storage type.
+     * @param internalSchemaExtensions Internal extensions ({@link InternalConfiguration})
+     *      of configuration schemas ({@link ConfigurationRoot} and {@link Config}).
+     * @throws IllegalArgumentException If the configuration type of the root keys is not equal to the storage type,
+     *      or if the schema or its extensions are not valid.
      */
     public ConfigurationRegistry(
         Collection<RootKey<?, ?>> rootKeys,
         Map<Class<? extends Annotation>, Set<Validator<? extends Annotation, ?>>> validators,
-        ConfigurationStorage storage
+        ConfigurationStorage storage,
+        Collection<Class<?>> internalSchemaExtensions
     ) {
         checkConfigurationType(rootKeys, storage);
 
+        Set<Class<?>> allSchemas = collectSchemas(rootKeys.stream().map(RootKey::schemaClass).collect(toSet()));
+
+        Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(internalSchemaExtensions);
+
+        if (!allSchemas.containsAll(extensions.keySet())) {
+            Set<Class<?>> notInAllSchemas = extensions.keySet().stream()
+                .filter(not(allSchemas::contains))
+                .collect(toSet());
+
+            throw new IllegalArgumentException(
+                "Internal extensions for which no parent configuration schemes were found: " + notInAllSchemas
+            );
+        }
+
         this.rootKeys = rootKeys;
 
         Map<Class<? extends Annotation>, Set<Validator<?, ?>>> validators0 = new HashMap<>(validators);
@@ -99,25 +124,24 @@ public class ConfigurationRegistry implements IgniteComponent {
                 return cgen.instantiateNode(rootKey.schemaClass());
             }
         };
-    }
 
-    /** {@inheritDoc} */
-    @Override public void start() {
         rootKeys.forEach(rootKey -> {
-            cgen.compileRootSchema(rootKey.schemaClass());
+            cgen.compileRootSchema(rootKey.schemaClass(), extensions);
 
             DynamicConfiguration<?, ?> cfg = cgen.instantiateCfg(rootKey, changer);
 
             configs.put(rootKey.key(), cfg);
         });
+    }
 
+    /** {@inheritDoc} */
+    @Override public void start() {
         changer.start();
     }
 
     /** {@inheritDoc} */
     @Override public void stop() {
-        if (changer != null)
-            changer.stop();
+        changer.stop();
     }
 
     /**
@@ -160,7 +184,7 @@ public class ConfigurationRegistry implements IgniteComponent {
 
         Object node;
         try {
-            node = ConfigurationUtil.find(path, superRoot);
+            node = ConfigurationUtil.find(path, superRoot, false);
         }
         catch (KeyNotFoundException e) {
             throw new IllegalArgumentException(e.getMessage());
@@ -198,7 +222,7 @@ public class ConfigurationRegistry implements IgniteComponent {
         newSuperRoot.traverseChildren(new ConfigurationVisitor<Void>() {
             /** {@inheritDoc} */
             @Override public Void visitInnerNode(String key, InnerNode newRoot) {
-                InnerNode oldRoot = oldSuperRoot.traverseChild(key, innerNodeVisitor());
+                InnerNode oldRoot = oldSuperRoot.traverseChild(key, innerNodeVisitor(), true);
 
                 var cfg = (DynamicConfiguration<InnerNode, ?>)configs.get(key);
 
@@ -209,7 +233,7 @@ public class ConfigurationRegistry implements IgniteComponent {
 
                 return null;
             }
-        });
+        }, true);
 
         // Map futures is only for logging errors.
         Function<CompletableFuture<?>, CompletableFuture<?>> mapping = fut -> fut.whenComplete((res, throwable) -> {
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java
index 010f34d..ebe8538 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java
@@ -73,15 +73,18 @@ public abstract class DynamicConfiguration<VIEW, CHANGE> extends ConfigurationNo
         assert keys instanceof RandomAccess;
 
         ConfigurationSource src = new ConfigurationSource() {
+            /** Current index in the {@code keys}. */
             private int level = 0;
 
+            /** {@inheritDoc} */
             @Override public void descend(ConstructableTreeNode node) {
                 if (level == keys.size())
                     change.accept((CHANGE)node);
                 else
-                    node.construct(keys.get(level++), this);
+                    node.construct(keys.get(level++), this, true);
             }
 
+            /** {@inheritDoc} */
             @Override public void reset() {
                 level = 0;
             }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicProperty.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicProperty.java
index 769c489..3203c56 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicProperty.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicProperty.java
@@ -60,14 +60,17 @@ public class DynamicProperty<T extends Serializable> extends ConfigurationNode<T
         assert !keys.isEmpty();
 
         ConfigurationSource src = new ConfigurationSource() {
+            /** Current index in the {@code keys}. */
             private int level = 0;
 
+            /** {@inheritDoc} */
             @Override public void descend(ConstructableTreeNode node) {
                 assert level < keys.size();
 
-                node.construct(keys.get(level++), this);
+                node.construct(keys.get(level++), this, true);
             }
 
+            /** {@inheritDoc} */
             @Override public <T> T unwrap(Class<T> clazz) {
                 assert level == keys.size();
 
@@ -76,6 +79,7 @@ public class DynamicProperty<T extends Serializable> extends ConfigurationNode<T
                 return clazz.cast(newValue);
             }
 
+            /** {@inheritDoc} */
             @Override public void reset() {
                 level = 0;
             }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/RootInnerNode.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/RootInnerNode.java
new file mode 100644
index 0000000..2c3ce71
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/RootInnerNode.java
@@ -0,0 +1,71 @@
+/*
+ * 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 org.apache.ignite.configuration.RootKey;
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.internal.configuration.tree.InnerNode;
+
+/**
+ * Holder of root configuration.
+ */
+public class RootInnerNode {
+    /** Root node. */
+    private final InnerNode node;
+
+    /** Internal configuration. */
+    private final boolean internal;
+
+    /**
+     * Constructor.
+     *
+     * @param node Root node.
+     * @param key Root key.
+     */
+    public RootInnerNode(RootKey<?, ?> key, InnerNode node) {
+        this.node = node;
+
+        internal = key.internal();
+    }
+
+    /**
+     * Copy constructor.
+     */
+    public RootInnerNode(RootInnerNode rootInnerNode) {
+        node = rootInnerNode.node.copy();
+        internal = rootInnerNode.internal;
+    }
+
+    /**
+     * Get root node.
+     *
+     * @return Root node.
+     */
+    public InnerNode node() {
+        return node;
+    }
+
+    /**
+     * Check if the root configuration is marked with {@link InternalConfiguration}.
+     *
+     * @return {@code true} if the root configuration is internal.
+     */
+    public boolean internal() {
+        return internal;
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/SuperRoot.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/SuperRoot.java
index 887256f..0fa3d08 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/SuperRoot.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/SuperRoot.java
@@ -17,8 +17,6 @@
 
 package org.apache.ignite.internal.configuration;
 
-import java.util.LinkedHashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.SortedMap;
@@ -28,16 +26,21 @@ import org.apache.ignite.configuration.RootKey;
 import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
 import org.apache.ignite.internal.configuration.tree.ConfigurationVisitor;
 import org.apache.ignite.internal.configuration.tree.InnerNode;
+import org.jetbrains.annotations.Nullable;
 
-/** */
+/**
+ * Holder of root configurations.
+ */
 public final class SuperRoot extends InnerNode {
-    /** */
-    private final SortedMap<String, InnerNode> roots = new TreeMap<>();
+    /** Root configurations. Mapping: {@link RootKey#key} -> configuration. */
+    private final SortedMap<String, RootInnerNode> roots = new TreeMap<>();
 
-    /** */
-    private final Function<String, InnerNode> nodeCreator;
+    /** Function that creates root node by root name or returns {@code null} if root name is not found. */
+    private final Function<String, RootInnerNode> nodeCreator;
 
-    /** Copy constructor. */
+    /**
+     * Copy constructor.
+     */
     private SuperRoot(SuperRoot superRoot) {
         roots.putAll(superRoot.roots);
 
@@ -45,92 +48,94 @@ public final class SuperRoot extends InnerNode {
     }
 
     /**
-     * @param nodeCreator Function that creates root node by root name or returns {@code null} if root name is not found
+     * Constructor.
+     *
+     * @param nodeCreator Function that creates root node by root name or returns {@code null} if root name is not found.
      */
-    public SuperRoot(Function<String, InnerNode> nodeCreator) {
-        this.nodeCreator = nodeCreator;
+    public SuperRoot(Function<String, RootInnerNode> nodeCreator) {
+        this(nodeCreator, Map.of());
     }
 
     /**
-     * @param nodeCreator Function that creates root node by root name or returns {@code null} if root name is not found
+     * Constructor.
+     *
+     * @param nodeCreator Function that creates root node by root name or returns {@code null} if root name is not found.
      * @param roots Map of roots belonging to this super root.
      */
-    public SuperRoot(Function<String, InnerNode> nodeCreator, Map<RootKey<?, ?>, InnerNode> roots) {
+    public SuperRoot(Function<String, RootInnerNode> nodeCreator, Map<RootKey<?, ?>, InnerNode> roots) {
         this.nodeCreator = nodeCreator;
 
         for (Map.Entry<RootKey<?, ?>, InnerNode> entry : roots.entrySet())
-            this.roots.put(entry.getKey().key(), entry.getValue().copy());
-    }
-
-    /**
-     * @param nodeCreator Function that creates root node by root name or returns {@code null} if root name is not found
-     * @param superRoots List of super roots to merge into even more superior root.
-     */
-    public SuperRoot(Function<String, InnerNode> nodeCreator, List<SuperRoot> superRoots) {
-        this(nodeCreator);
-
-        for (SuperRoot superRoot : superRoots)
-            roots.putAll(superRoot.roots);
+            this.roots.put(entry.getKey().key(), new RootInnerNode(entry.getKey(), entry.getValue().copy()));
     }
 
     /**
      * Adds a root to the super root.
+     *
      * @param rootKey Root key.
      * @param root Root node.
      */
     public void addRoot(RootKey<?, ?> rootKey, InnerNode root) {
         assert !roots.containsKey(rootKey.key()) : rootKey.key() + " : " + roots;
 
-        roots.put(rootKey.key(), root);
+        roots.put(rootKey.key(), new RootInnerNode(rootKey, root));
     }
 
     /**
      * Gets a root.
+     *
      * @param rootKey Root key of the desired root.
      * @return Root node.
      */
-    public InnerNode getRoot(RootKey<?, ?> rootKey) {
-        return roots.get(rootKey.key());
+    @Nullable public InnerNode getRoot(RootKey<?, ?> rootKey) {
+        RootInnerNode root = roots.get(rootKey.key());
+
+        return root == null ? null : root.node();
     }
 
     /** {@inheritDoc} */
-    @Override public <T> void traverseChildren(ConfigurationVisitor<T> visitor) {
-        for (String key : new LinkedHashSet<>(roots.keySet()))
-            visitor.visitInnerNode(key, roots.get(key));
+    @Override public <T> void traverseChildren(ConfigurationVisitor<T> visitor, boolean includeInternal) {
+        for (Map.Entry<String, RootInnerNode> e : roots.entrySet()) {
+            if (includeInternal || !e.getValue().internal())
+                visitor.visitInnerNode(e.getKey(), e.getValue().node());
+        }
     }
 
     /** {@inheritDoc} */
-    @Override public <T> T traverseChild(String key, ConfigurationVisitor<T> visitor) throws NoSuchElementException {
-        InnerNode root = roots.get(key);
-
-        if (root == null)
+    @Override public <T> T traverseChild(
+        String key,
+        ConfigurationVisitor<T> visitor,
+        boolean includeInternal
+    ) throws NoSuchElementException {
+        RootInnerNode root = roots.get(key);
+
+        if (root == null || (!includeInternal && root.internal()))
             throw new NoSuchElementException(key);
-
-        return visitor.visitInnerNode(key, root);
+        else
+            return visitor.visitInnerNode(key, root.node());
     }
 
     /** {@inheritDoc} */
-    @Override public void construct(String key, ConfigurationSource src) throws NoSuchElementException {
-        if (src == null) {
-            roots.remove(key);
-
-            return;
-        }
-
-        InnerNode root = roots.get(key);
+    @Override public void construct(
+        String key,
+        ConfigurationSource src,
+        boolean includeInternal
+    ) throws NoSuchElementException {
+        RootInnerNode root = roots.get(key);
 
-        if (root == null) {
+        if (root == null)
             root = nodeCreator.apply(key);
 
-            if (root == null)
-                throw new NoSuchElementException(key);
-        }
-        else
-            root = root.copy();
+        if (root == null || !includeInternal && root.internal())
+            throw new NoSuchElementException(key);
 
-        roots.put(key, root);
+        if (src == null)
+            roots.remove(key);
+        else {
+            roots.put(key, root = new RootInnerNode(root));
 
-        src.descend(root);
+            src.descend(root.node());
+        }
     }
 
     /** {@inheritDoc} */
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 261ce9c..2479744 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
@@ -26,7 +26,7 @@ import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -40,7 +40,9 @@ import java.util.Set;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
+import java.util.stream.Stream;
 import com.facebook.presto.bytecode.BytecodeBlock;
+import com.facebook.presto.bytecode.BytecodeNode;
 import com.facebook.presto.bytecode.ClassDefinition;
 import com.facebook.presto.bytecode.ClassGenerator;
 import com.facebook.presto.bytecode.FieldDefinition;
@@ -55,8 +57,8 @@ import org.apache.ignite.configuration.NamedConfigurationTree;
 import org.apache.ignite.configuration.NamedListView;
 import org.apache.ignite.configuration.RootKey;
 import org.apache.ignite.configuration.annotation.Config;
-import org.apache.ignite.configuration.annotation.ConfigValue;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.internal.configuration.ConfigurationChanger;
@@ -74,6 +76,7 @@ import org.objectweb.asm.Handle;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 
+import static com.facebook.presto.bytecode.Access.BRIDGE;
 import static com.facebook.presto.bytecode.Access.FINAL;
 import static com.facebook.presto.bytecode.Access.PRIVATE;
 import static com.facebook.presto.bytecode.Access.PUBLIC;
@@ -91,8 +94,19 @@ import static com.facebook.presto.bytecode.expression.BytecodeExpressions.invoke
 import static com.facebook.presto.bytecode.expression.BytecodeExpressions.isNull;
 import static com.facebook.presto.bytecode.expression.BytecodeExpressions.newInstance;
 import static java.lang.invoke.MethodType.methodType;
+import static java.util.Arrays.asList;
 import static java.util.Collections.singleton;
 import static java.util.EnumSet.of;
+import static java.util.stream.Collectors.toList;
+import static org.apache.ignite.internal.configuration.asm.SchemaClassesInfo.changeClassName;
+import static org.apache.ignite.internal.configuration.asm.SchemaClassesInfo.configurationClassName;
+import static org.apache.ignite.internal.configuration.asm.SchemaClassesInfo.viewClassName;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.extensionsFields;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.isConfigValue;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.isNamedConfigValue;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.isValue;
+import static org.apache.ignite.internal.util.CollectionUtils.concat;
+import static org.apache.ignite.internal.util.CollectionUtils.union;
 import static org.objectweb.asm.Opcodes.H_NEWINVOKESPECIAL;
 import static org.objectweb.asm.Type.getMethodDescriptor;
 import static org.objectweb.asm.Type.getMethodType;
@@ -241,10 +255,17 @@ public class ConfigurationAsmGenerator {
     }
 
     /**
-     * Generates, defines, loads and initializes all dynamic classes required for the given Configuration Schema.
-     * @param rootSchemaClass Class of the root Configuration Schema.
+     * Generates, defines, loads and initializes all dynamic classes required for the given configuration schema.
+     *
+     * @param rootSchemaClass Class of the root configuration schema.
+     * @param internalSchemaExtensions Internal extensions ({@link InternalConfiguration})
+     *      of configuration schemas ({@link ConfigurationRoot} and {@link Config}).
+     *      Mapping: original schema -> extensions.
      */
-    public synchronized void compileRootSchema(Class<?> rootSchemaClass) {
+    public synchronized void compileRootSchema(
+        Class<?> rootSchemaClass,
+        Map<Class<?>, Set<Class<?>>> internalSchemaExtensions
+    ) {
         if (schemasInfo.containsKey(rootSchemaClass))
             return; // Already compiled.
 
@@ -263,11 +284,16 @@ public class ConfigurationAsmGenerator {
                 || schemaClass.isAnnotationPresent(Config.class)
                 : schemaClass + " is not properly annotated";
 
-            assert schemasInfo.containsKey(schemaClass);
+            assert schemasInfo.containsKey(schemaClass) : schemaClass;
+
+            Field[] schemaFields = schemaClass.getDeclaredFields();
+
+            Set<Class<?>> schemaExtensions = internalSchemaExtensions.getOrDefault(schemaClass, Set.of());
+            Set<Field> extensionsFields = extensionsFields(schemaExtensions);
 
-            for (Field field : schemaClass.getDeclaredFields()) {
-                if (isConfigValue(field) || isNamedConfigValue(field)) {
-                    Class<?> subSchemaClass = field.getType();
+            for (Field schemaField : concat(asList(schemaFields), extensionsFields)) {
+                if (isConfigValue(schemaField) || isNamedConfigValue(schemaField)) {
+                    Class<?> subSchemaClass = schemaField.getType();
 
                     if (!schemasInfo.containsKey(subSchemaClass)) {
                         compileQueue.offer(subSchemaClass);
@@ -277,8 +303,8 @@ public class ConfigurationAsmGenerator {
             }
 
             schemas.add(schemaClass);
-            definitions.add(createNodeClass(schemaClass));
-            definitions.add(createCfgImplClass(schemaClass));
+            definitions.add(createNodeClass(schemaClass, schemaExtensions, schemaFields, extensionsFields));
+            definitions.add(createCfgImplClass(schemaClass, schemaExtensions, schemaFields, extensionsFields));
         }
 
         Map<String, Class<?>> definedClasses = generator.defineClasses(definitions);
@@ -291,7 +317,21 @@ public class ConfigurationAsmGenerator {
         }
     }
 
-    private ClassDefinition createNodeClass(Class<?> schemaClass) {
+    /**
+     * Construct a {@link InnerNode} definition for a configuration schema.
+     *
+     * @param schemaClass Configuration schema class.
+     * @param schemaExtensions Internal extensions of the configuration schema.
+     * @param schemaFields Fields of the schema class.
+     * @param extensionsFields Fields of internal extensions of the configuration schema.
+     * @return Constructed {@link InnerNode} definition for the configuration schema.
+     */
+    private ClassDefinition createNodeClass(
+        Class<?> schemaClass,
+        Set<Class<?>> schemaExtensions,
+        Field[] schemaFields,
+        Set<Field> extensionsFields
+    ) {
         SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
 
         // Node class definition.
@@ -299,83 +339,61 @@ public class ConfigurationAsmGenerator {
             of(PUBLIC, FINAL),
             internalName(schemaClassInfo.nodeClassName),
             type(InnerNode.class),
-            typeFromJavaClassName(schemaClassInfo.viewClassName),
-            typeFromJavaClassName(schemaClassInfo.changeClassName)
+            nodeClassInterfaces(schemaClass, schemaExtensions)
         );
 
-        // Spec field.
-        FieldDefinition specField = classDef.declareField(of(PRIVATE, FINAL), "_spec", schemaClass);
+        // Spec fields.
+        Map<Class<?>, FieldDefinition> specFields = new HashMap<>();
 
-        // org.apache.ignite.internal.configuration.tree.InnerNode#schemaType
-        addNodeSchemaTypeMethod(classDef, specField);
+        int i = 0;
 
-        // Cached array of reflected fields. Helps to avoid unnecessary clonings.
-        Field[] schemaFields = schemaClass.getDeclaredFields();
+        for (Class<?> clazz : concat(List.of(schemaClass), schemaExtensions))
+            specFields.put(clazz, classDef.declareField(of(PRIVATE, FINAL), "_spec" + i++, clazz));
+
+        // org.apache.ignite.internal.configuration.tree.InnerNode#schemaType
+        addNodeSchemaTypeMethod(classDef, specFields.get(schemaClass));
 
         // Define the rest of the fields.
         Map<String, FieldDefinition> fieldDefs = new HashMap<>();
 
-        for (Field schemaField : schemaFields) {
+        for (Field schemaField : concat(asList(schemaFields), extensionsFields)) {
             assert isValue(schemaField) || isConfigValue(schemaField) || isNamedConfigValue(schemaField) : schemaField;
 
             fieldDefs.put(schemaField.getName(), addNodeField(classDef, schemaField));
         }
 
         // Constructor.
-        addNodeConstructor(classDef, schemaClass, specField, schemaFields, fieldDefs);
+        addNodeConstructor(classDef, specFields, fieldDefs, schemaFields, extensionsFields);
 
         // VIEW and CHANGE methods.
-        for (Field schemaField : schemaFields) {
-            FieldDefinition fieldDef = fieldDefs.get(schemaField.getName());
+        for (Field schemaField : concat(asList(schemaFields), extensionsFields)) {
+            String fieldName = schemaField.getName();
+
+            FieldDefinition fieldDef = fieldDefs.get(fieldName);
 
             addNodeViewMethod(classDef, schemaField, fieldDef);
 
-            addNodeChangeMethod(classDef, schemaField, fieldDef, schemaClassInfo);
+            // Add change methods.
+            MethodDefinition changeMtd = addNodeChangeMethod(classDef, schemaField, fieldDef, schemaClassInfo.nodeClassName);
+            addNodeChangeBridgeMethod(classDef, changeClassName(schemaField.getDeclaringClass()), changeMtd);
         }
 
         // traverseChildren
-        addNodeTraverseChildrenMethod(classDef, schemaFields, fieldDefs);
+        addNodeTraverseChildrenMethod(classDef, fieldDefs, schemaFields, extensionsFields);
 
         // traverseChild
-        addNodeTraverseChildMethod(classDef, schemaFields, fieldDefs);
+        addNodeTraverseChildMethod(classDef, fieldDefs, schemaFields, extensionsFields);
 
         // construct
-        addNodeConstructMethod(classDef, schemaFields, fieldDefs);
+        addNodeConstructMethod(classDef, fieldDefs, schemaFields, extensionsFields);
 
         // constructDefault
-        addNodeConstructDefaultMethod(classDef, specField, schemaFields, fieldDefs);
+        addNodeConstructDefaultMethod(classDef, specFields, fieldDefs, schemaFields, extensionsFields);
 
         return classDef;
     }
 
     /**
-     * Checks whether configuration schema field represents primitive configuration value.
-     * @param schemaField Configuration Schema class field.
-     * @return {@code true} if field represents primitive configuration.
-     */
-    private static boolean isValue(Field schemaField) {
-        return schemaField.isAnnotationPresent(Value.class);
-    }
-
-    /**
-     * Checks whether configuration schema field represents regular configuration value.
-     * @param schemaField Configuration Schema class field.
-     * @return {@code true} if field represents regular configuration.
-     */
-    private static boolean isConfigValue(Field schemaField) {
-        return schemaField.isAnnotationPresent(ConfigValue.class);
-    }
-
-    /**
-     * Checks whether configuration schema field represents named list configuration value.
-     * @param schemaField Configuration Schema class field.
-     * @return {@code true} if field represents named list configuration.
-     */
-    private static boolean isNamedConfigValue(Field schemaField) {
-        return schemaField.isAnnotationPresent(NamedConfigValue.class);
-    }
-
-    /**
      * Add {@link InnerNode#schemaType()} method implementation to the class. It looks like the following code:
      * <pre><code>
      * public Class schemaType() {
@@ -431,28 +449,31 @@ public class ConfigurationAsmGenerator {
     /**
      * Implements default constructor for the node class. It initializes {@code _spec} field and every other field
      * that represents named list configuration.
+     *
      * @param classDef Node class definition.
-     * @param schemaClass Configuration Schema class.
-     * @param specField Field definition for the {@code _spec} field of the node class.
-     * @param schemaFields Configuration Schema class fields.
+     * @param specFields Definition of fields for the {@code _spec#} fields of the node class.
+     *      Mapping: configuration schema class -> {@code _spec#} field.
      * @param fieldDefs Field definitions for all fields of node class excluding {@code _spec}.
+     * @param schemaFields Fields of the schema class.
+     * @param extensionsFields Fields of internal extensions of the configuration schema.
      */
     private void addNodeConstructor(
         ClassDefinition classDef,
-        Class<?> schemaClass,
-        FieldDefinition specField,
+        Map<Class<?>, FieldDefinition> specFields,
+        Map<String, FieldDefinition> fieldDefs,
         Field[] schemaFields,
-        Map<String, FieldDefinition> fieldDefs
+        Set<Field> extensionsFields
     ) {
         MethodDefinition ctor = classDef.declareConstructor(of(PUBLIC));
 
         // super();
         ctor.getBody().append(ctor.getThis()).invokeConstructor(InnerNode.class);
 
-        // this._spec = new MyConfigurationSchema();
-        ctor.getBody().append(ctor.getThis().setField(specField, newInstance(schemaClass)));
+        // this._spec# = new MyConfigurationSchema();
+        for (Map.Entry<Class<?>, FieldDefinition> e : specFields.entrySet())
+            ctor.getBody().append(ctor.getThis().setField(e.getValue(), newInstance(e.getKey())));
 
-        for (Field schemaField : schemaFields) {
+        for (Field schemaField : concat(asList(schemaFields), extensionsFields)) {
             if (!isNamedConfigValue(schemaField))
                 continue;
 
@@ -480,7 +501,7 @@ public class ConfigurationAsmGenerator {
      * depending on type.
      * @param classDef Node class definition.
      * @param schemaField Configuration Schema class field.
-     * @param fieldDefs Field definition.
+     * @param fieldDef Field definition.
      */
     private void addNodeViewMethod(
         ClassDefinition classDef,
@@ -529,23 +550,25 @@ public class ConfigurationAsmGenerator {
 
     /**
      * Implements changer method from {@code CHANGE} interface.
+     *
      * @param classDef Node class definition.
      * @param schemaField Configuration Schema class field.
      * @param fieldDef Field definition.
-     * @param schemaClassInfo {@link SchemaClassesInfo} for Configuration Schema that contains original field.
+     * @param nodeClassName Class name for the Node class.
+     * @return Definition of change method.
      */
-    private void addNodeChangeMethod(
+    private MethodDefinition addNodeChangeMethod(
         ClassDefinition classDef,
         Field schemaField,
         FieldDefinition fieldDef,
-        SchemaClassesInfo schemaClassInfo
+        String nodeClassName
     ) {
         Class<?> schemaFieldType = schemaField.getType();
 
         MethodDefinition changeMtd = classDef.declareMethod(
             of(PUBLIC),
             "change" + capitalize(schemaField.getName()),
-            typeFromJavaClassName(schemaClassInfo.changeClassName),
+            typeFromJavaClassName(nodeClassName),
             // Change argument type is a Consumer for all inner or named fields.
             arg("change", isValue(schemaField) ? type(schemaFieldType) : type(Consumer.class))
         );
@@ -585,86 +608,123 @@ public class ConfigurationAsmGenerator {
 
         // return this;
         changeBody.append(changeMtd.getThis()).retObject();
+
+        return changeMtd;
     }
 
     /**
-     * Implements {@link InnerNode#traverseChildren(ConfigurationVisitor)} method.
+     * Implements changer bridge method from {@code CHANGE} interface.
+     *
+     * @param classDef Node class definition.
+     * @param changeClassName Class name for the CHANGE class.
+     * @param changeMtd Definition of change method.
+     */
+    private void addNodeChangeBridgeMethod(
+        ClassDefinition classDef,
+        String changeClassName,
+        MethodDefinition changeMtd
+    ) {
+        MethodDefinition bridgeMtd = classDef.declareMethod(
+            of(PUBLIC, SYNTHETIC, BRIDGE),
+            changeMtd.getName(),
+            typeFromJavaClassName(changeClassName),
+            changeMtd.getParameters()
+        );
+
+        Variable changeVar = bridgeMtd.getScope().getVariable("change");
+
+        // this.change*(change);
+        BytecodeExpression invokeChangeMtd = bridgeMtd.getThis().invoke(changeMtd, List.of(changeVar));
+
+        // return this.change*(change);
+        bridgeMtd.getBody().append(invokeChangeMtd).retObject();
+    }
+
+    /**
+     * Implements {@link InnerNode#traverseChildren(ConfigurationVisitor, boolean)} method.
+     *
      * @param classDef Class definition.
-     * @param schemaFields Array of all schema fields that represent configurations.
      * @param fieldDefs Definitions for all fields in {@code schemaFields}.
+     * @param schemaFields Fields of the schema class.
+     * @param extensionsFields Fields of internal extensions of the configuration schema.
      */
     private void addNodeTraverseChildrenMethod(
         ClassDefinition classDef,
+        Map<String, FieldDefinition> fieldDefs,
         Field[] schemaFields,
-        Map<String, FieldDefinition> fieldDefs
+        Set<Field> extensionsFields
     ) {
         MethodDefinition traverseChildrenMtd = classDef.declareMethod(
             of(PUBLIC),
             "traverseChildren",
             type(void.class),
-            arg("visitor", type(ConfigurationVisitor.class))
+            arg("visitor", type(ConfigurationVisitor.class)),
+            arg("includeInternal", type(boolean.class))
         ).addException(NoSuchElementException.class);
 
         BytecodeBlock mtdBody = traverseChildrenMtd.getBody();
 
-        for (Field schemaField : schemaFields) {
-            FieldDefinition fieldDef = fieldDefs.get(schemaField.getName());
+        invokeVisitForTraverseChildren(asList(schemaFields), fieldDefs, traverseChildrenMtd)
+            .forEach(mtdBody::append);
+
+        if (!extensionsFields.isEmpty()) {
+            Variable includeInternalVar = traverseChildrenMtd.getScope().getVariable("includeInternal");
+
+            BytecodeBlock includeInternalBlock = new BytecodeBlock();
 
-            // Visit every field. Returned value isn't used so we pop it off the stack.
-            mtdBody.append(invokeVisit(traverseChildrenMtd, schemaField, fieldDef).pop());
+            invokeVisitForTraverseChildren(extensionsFields, fieldDefs, traverseChildrenMtd)
+                .forEach(includeInternalBlock::append);
+
+            mtdBody.append(new IfStatement().condition(includeInternalVar).ifTrue(includeInternalBlock));
         }
 
         mtdBody.ret();
     }
 
     /**
-     * Implements {@link InnerNode#traverseChild(String, ConfigurationVisitor)} method.
+     * Implements {@link InnerNode#traverseChild(String, ConfigurationVisitor, boolean)} method.
+     *
      * @param classDef Class definition.
-     * @param schemaFields Array of all schema fields that represent configurations.
      * @param fieldDefs Definitions for all fields in {@code schemaFields}.
+     * @param schemaFields Fields of the schema class.
+     * @param extensionsFields Fields of internal extensions of the configuration schema.
      */
     private void addNodeTraverseChildMethod(
         ClassDefinition classDef,
+        Map<String, FieldDefinition> fieldDefs,
         Field[] schemaFields,
-        Map<String, FieldDefinition> fieldDefs
+        Set<Field> extensionsFields
     ) {
         MethodDefinition traverseChildMtd = classDef.declareMethod(
             of(PUBLIC),
             "traverseChild",
             type(Object.class),
             arg("key", type(String.class)),
-            arg("visitor", type(ConfigurationVisitor.class))
+            arg("visitor", type(ConfigurationVisitor.class)),
+            arg("includeInternal", type(boolean.class))
         ).addException(NoSuchElementException.class);
 
-        Variable keyVar = traverseChildMtd.getScope().getVariable("key");
-
-        // Here we have a switch by passed kay parameter.
-        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(traverseChildMtd.getScope())
-            .expression(keyVar);
-
-        for (Field schemaField : schemaFields) {
-            String fieldName = schemaField.getName();
+        BytecodeBlock mtdBody = traverseChildMtd.getBody();
 
-            FieldDefinition fieldDef = fieldDefs.get(fieldName);
+        if (extensionsFields.isEmpty())
+            mtdBody.append(invokeVisitForTraverseChild(asList(schemaFields), fieldDefs, traverseChildMtd));
+        else {
+            Variable includeInternalVar = traverseChildMtd.getScope().getVariable("includeInternal");
 
-            // Visit result should be immediately returned.
-            switchBuilder.addCase(fieldName, invokeVisit(traverseChildMtd, schemaField, fieldDef).retObject());
+            mtdBody.append(
+                new IfStatement()
+                    .condition(includeInternalVar)
+                    .ifTrue(invokeVisitForTraverseChild(union(extensionsFields, schemaFields), fieldDefs, traverseChildMtd))
+                    .ifFalse(invokeVisitForTraverseChild(asList(schemaFields), fieldDefs, traverseChildMtd))
+            );
         }
-
-        // Default option is to throw "NoSuchElementException(key)".
-        switchBuilder.defaultCase(new BytecodeBlock()
-            .append(newInstance(NoSuchElementException.class, keyVar))
-            .throwObject()
-        );
-
-        // No default "return" statement required.
-        traverseChildMtd.getBody().append(switchBuilder.build());
     }
 
     /**
      * Creates bytecode block that invokes one of {@link ConfigurationVisitor}'s methods.
-     * @param mtd Method definition, either {@link InnerNode#traverseChildren(ConfigurationVisitor)} or
-     *      {@link InnerNode#traverseChild(String, ConfigurationVisitor)} defined in {@code *Node} class.
+     *
+     * @param mtd Method definition, either {@link InnerNode#traverseChildren(ConfigurationVisitor, boolean)} or
+     *      {@link InnerNode#traverseChild(String, ConfigurationVisitor, boolean)} defined in {@code *Node} class.
      * @param schemaField Configuration Schema field to visit.
      * @param fieldDef Field definition from current class.
      * @return Bytecode block that invokes "visit*" method.
@@ -687,104 +747,60 @@ public class ConfigurationAsmGenerator {
     }
 
     /**
-     * Implements {@link ConstructableTreeNode#construct(String, ConfigurationSource)} method.
+     * Implements {@link ConstructableTreeNode#construct(String, ConfigurationSource, boolean)} method.
+     *
      * @param classDef Class definition.
-     * @param schemaFields Array of all schema fields that represent configurations.
      * @param fieldDefs Definitions for all fields in {@code schemaFields}.
+     * @param schemaFields Fields of the schema class.
+     * @param extensionsFields Fields of internal extensions of the configuration schema.
      */
     private void addNodeConstructMethod(
         ClassDefinition classDef,
+        Map<String, FieldDefinition> fieldDefs,
         Field[] schemaFields,
-        Map<String, FieldDefinition> fieldDefs
+        Set<Field> extensionsFields
     ) {
         MethodDefinition constructMtd = classDef.declareMethod(
             of(PUBLIC),
             "construct",
             type(void.class),
-            arg("key", String.class),
-            arg("src", ConfigurationSource.class)
+            arg("key", type(String.class)),
+            arg("src", type(ConfigurationSource.class)),
+            arg("includeInternal", type(boolean.class))
         ).addException(NoSuchElementException.class);
 
-        Variable keyVar = constructMtd.getScope().getVariable("key");
-        Variable srcVar = constructMtd.getScope().getVariable("src");
-
-        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(constructMtd.getScope())
-            .expression(keyVar);
-
-        for (Field schemaField : schemaFields) {
-            FieldDefinition fieldDef = fieldDefs.get(schemaField.getName());
-
-            BytecodeBlock caseClause = new BytecodeBlock();
-
-            switchBuilder.addCase(schemaField.getName(), caseClause);
-
-            // this.field = src == null ? null : src.unwrap(FieldType.class);
-            if (isValue(schemaField)) {
-                caseClause.append(constructMtd.getThis().setField(fieldDef, inlineIf(
-                    isNull(srcVar),
-                    constantNull(fieldDef.getType()),
-                    srcVar.invoke(UNWRAP, constantClass(fieldDef.getType())).cast(fieldDef.getType())
-                )));
-            }
-            // this.field = src == null ? null : src.descend(field = (field == null ? new FieldType() : field.copy()));
-            else if (isConfigValue(schemaField)) {
-                caseClause.append(new IfStatement()
-                    .condition(isNull(srcVar))
-                    .ifTrue(constructMtd.getThis().setField(fieldDef, constantNull(fieldDef.getType())))
-                    .ifFalse(new BytecodeBlock()
-                        .append(copyNodeField(constructMtd, fieldDef))
-                        .append(srcVar.invoke(DESCEND, constructMtd.getThis().getField(fieldDef)))
-                    )
-                );
-            }
-            // 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;
+        BytecodeBlock mtdBody = constructMtd.getBody();
 
-                caseClause.append(new IfStatement()
-                    .condition(isNull(srcVar))
-                    .ifTrue(constructMtd.getThis().setField(
-                        fieldDef,
-                        newInstance(
-                            NamedListNode.class,
-                            constantString(namedCfgAnnotation.syntheticKeyName()),
-                            newNamedListElementLambda(fieldNodeClassName)
-                        )
-                    ))
-                    .ifFalse(new BytecodeBlock()
-                        .append(constructMtd.getThis().setField(
-                            fieldDef,
-                            constructMtd.getThis().getField(fieldDef).invoke(COPY).cast(fieldDef.getType())
-                        ))
-                        .append(srcVar.invoke(DESCEND, constructMtd.getThis().getField(fieldDef)))
-                    )
-                );
-            }
+        if (extensionsFields.isEmpty())
+            mtdBody.append(treatSourceForConstruct(asList(schemaFields), fieldDefs, constructMtd)).ret();
+        else {
+            Variable includeInternalVar = constructMtd.getScope().getVariable("includeInternal");
+
+            mtdBody.append(
+                new IfStatement()
+                    .condition(includeInternalVar)
+                    .ifTrue(treatSourceForConstruct(union(extensionsFields, schemaFields), fieldDefs, constructMtd))
+                    .ifFalse(treatSourceForConstruct(asList(schemaFields), fieldDefs, constructMtd))
+            ).ret();
         }
-
-        // Default option is to throw "NoSuchElementException(key)".
-        switchBuilder.defaultCase(new BytecodeBlock()
-            .append(newInstance(NoSuchElementException.class, keyVar))
-            .throwObject()
-        );
-
-        constructMtd.getBody().append(switchBuilder.build()).ret();
     }
 
     /**
      * Implements {@link InnerNode#constructDefault(String)} method.
+     *
      * @param classDef Node class definition.
-     * @param specField Field definition for the {@code _spec} field of the node class.
-     * @param schemaFields Configuration Schema class fields.
+     * @param specFields Definition of fields for the {@code _spec#} fields of the node class.
+     *      Mapping: configuration schema class -> {@code _spec#} field.
      * @param fieldDefs Field definitions for all fields of node class excluding {@code _spec}.
+     * @param schemaFields Fields of the schema class.
+     * @param extensionsFields Fields of internal extensions of the configuration schema.
      */
     private void addNodeConstructDefaultMethod(
         ClassDefinition classDef,
-        FieldDefinition specField,
+        Map<Class<?>, FieldDefinition> specFields,
+        Map<String, FieldDefinition> fieldDefs,
         Field[] schemaFields,
-        Map<String, FieldDefinition> fieldDefs
+        Set<Field> extensionsFields
     ) {
         MethodDefinition constructDfltMtd = classDef.declareMethod(
             of(PUBLIC),
@@ -798,7 +814,7 @@ public class ConfigurationAsmGenerator {
         StringSwitchBuilder switchBuilder = new StringSwitchBuilder(constructDfltMtd.getScope())
             .expression(keyVar);
 
-        for (Field schemaField : schemaFields) {
+        for (Field schemaField : concat(asList(schemaFields), extensionsFields)) {
             if (!isValue(schemaField))
                 continue;
 
@@ -812,7 +828,8 @@ public class ConfigurationAsmGenerator {
 
             Class<?> schemaFieldType = schemaField.getType();
 
-            // defaultValue = _spec.field;
+            // defaultValue = _spec#.field;
+            FieldDefinition specField = specFields.get(schemaField.getDeclaringClass());
             BytecodeExpression defaultValue = constructDfltMtd.getThis().getField(specField).getField(schemaField);
 
             // defaultValue = Box.valueOf(defaultValue); // Boxing.
@@ -867,7 +884,7 @@ public class ConfigurationAsmGenerator {
     @NotNull private BytecodeExpression newNamedListElementLambda(String nodeClassName) {
         return invokeDynamic(
             LAMBDA_METAFACTORY,
-            Arrays.asList(
+            asList(
                 getMethodType(getType(Object.class)),
                 new Handle(
                     H_NEWINVOKESPECIAL,
@@ -883,7 +900,21 @@ public class ConfigurationAsmGenerator {
         );
     }
 
-    private ClassDefinition createCfgImplClass(Class<?> schemaClass) {
+    /**
+     * Construct a {@link DynamicConfiguration} definition for a configuration schema.
+     *
+     * @param schemaClass Configuration schema class.
+     * @param schemaExtensions Internal extensions of the configuration schema.
+     * @param schemaFields Fields of the schema class.
+     * @param extensionsFields Fields of internal extensions of the configuration schema.
+     * @return Constructed {@link DynamicConfiguration} definition for the configuration schema.
+     */
+    private ClassDefinition createCfgImplClass(
+        Class<?> schemaClass,
+        Set<Class<?>> schemaExtensions,
+        Field[] schemaFields,
+        Set<Field> extensionsFields
+    ) {
         SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
 
         // Configuration impl class definition.
@@ -891,24 +922,20 @@ public class ConfigurationAsmGenerator {
             of(PUBLIC, FINAL),
             internalName(schemaClassInfo.cfgImplClassName),
             type(DynamicConfiguration.class),
-            typeFromJavaClassName(schemaClassInfo.cfgClassName)
+            configClassInterfaces(schemaClass, schemaExtensions)
         );
 
-        // Cached array of reflected fields. Helps to avoid unnecessary clonings.
-        Field[] schemaFields = schemaClass.getDeclaredFields();
-
         // Fields.
         Map<String, FieldDefinition> fieldDefs = new HashMap<>();
 
-        for (Field schemaField : schemaFields)
+        for (Field schemaField : concat(asList(schemaFields), extensionsFields))
             fieldDefs.put(schemaField.getName(), addConfigurationImplField(classDef, schemaField));
 
         // Constructor
-        addConfigurationImplConstructor(classDef, schemaClassInfo, schemaFields, fieldDefs);
+        addConfigurationImplConstructor(classDef, schemaClassInfo, fieldDefs, schemaFields, extensionsFields);
 
-        for (Field schemaField : schemaFields) {
+        for (Field schemaField : concat(asList(schemaFields), extensionsFields))
             addConfigurationImplGetMethod(classDef, schemaClass, fieldDefs, schemaField);
-        }
 
         return classDef;
     }
@@ -951,14 +978,16 @@ public class ConfigurationAsmGenerator {
      * collection.
      * @param classDef Configuration impl class definition.
      * @param schemaClassInfo Configuration Schema class info.
-     * @param schemaFields Configuration Schema class fields.
      * @param fieldDefs Field definitions for all fields of configuration impl class.
+     * @param schemaFields Fields of the schema class.
+     * @param extensionsFields Fields of internal extensions of the configuration schema.
      */
     private void addConfigurationImplConstructor(
         ClassDefinition classDef,
         SchemaClassesInfo schemaClassInfo,
+        Map<String, FieldDefinition> fieldDefs,
         Field[] schemaFields,
-        Map<String, FieldDefinition> fieldDefs
+        Set<Field> extensionsFields
     ) {
         MethodDefinition ctor = classDef.declareConstructor(
             of(PUBLIC),
@@ -977,7 +1006,7 @@ public class ConfigurationAsmGenerator {
             .invokeConstructor(DYNAMIC_CONFIGURATION_CTOR);
 
         int newIdx = 0;
-        for (Field schemaField : schemaFields) {
+        for (Field schemaField : concat(asList(schemaFields), extensionsFields)) {
             FieldDefinition fieldDef = fieldDefs.get(schemaField.getName());
 
             BytecodeExpression newValue;
@@ -1031,7 +1060,7 @@ public class ConfigurationAsmGenerator {
                         ctor.getScope().getVariable("changer"),
                         invokeDynamic(
                             LAMBDA_METAFACTORY,
-                            Arrays.asList(
+                            asList(
                                 getMethodType(getType(Object.class), getType(Object.class), getType(Object.class)),
                                 new Handle(
                                     Opcodes.H_INVOKESTATIC,
@@ -1149,4 +1178,174 @@ public class ConfigurationAsmGenerator {
 
         return boxed == null ? clazz : boxed;
     }
+
+    /**
+     * Create bytecode blocks that invokes of {@link ConfigurationVisitor}'s methods for
+     * {@link InnerNode#traverseChildren(ConfigurationVisitor, boolean)}.
+     *
+     * @param schemaFields Fields of the schema.
+     * @param fieldDefs Definitions for all fields in {@code schemaFields}.
+     * @param traverseChildrenMtd Method definition {@link InnerNode#traverseChildren(ConfigurationVisitor, boolean)}
+     *      defined in {@code *Node} class.
+     * @return Created bytecode blocks that invokes of {@link ConfigurationVisitor}'s methods for fields.
+     */
+    private Collection<BytecodeNode> invokeVisitForTraverseChildren(
+        Collection<Field> schemaFields,
+        Map<String, FieldDefinition> fieldDefs,
+        MethodDefinition traverseChildrenMtd
+    ) {
+        if (schemaFields.isEmpty())
+            return List.of();
+        else {
+            return schemaFields.stream()
+                .map(field -> invokeVisit(traverseChildrenMtd, field, fieldDefs.get(field.getName())).pop())
+                .collect(toList());
+        }
+    }
+
+    /**
+     * Created switch bytecode block that invokes of {@link ConfigurationVisitor}'s methods for
+     *     {@link InnerNode#traverseChild(String, ConfigurationVisitor, boolean)}.
+     *
+     * @param schemaFields Fields of the schema.
+     * @param fieldDefs Definitions for all fields in {@code schemaFields}.
+     * @param traverseChildMtd Method definition {@link InnerNode#traverseChild(String, ConfigurationVisitor, boolean)}}
+     *      defined in {@code *Node} class.
+     * @return Created switch bytecode block that invokes of {@link ConfigurationVisitor}'s methods for fields.
+     */
+    private BytecodeNode invokeVisitForTraverseChild(
+        Collection<Field> schemaFields,
+        Map<String, FieldDefinition> fieldDefs,
+        MethodDefinition traverseChildMtd
+    ) {
+        Variable keyVar = traverseChildMtd.getScope().getVariable("key");
+
+        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(traverseChildMtd.getScope()).expression(keyVar);
+
+        for (Field schemaField : schemaFields) {
+            String fieldName = schemaField.getName();
+
+            FieldDefinition fieldDef = fieldDefs.get(fieldName);
+
+            // Visit result should be immediately returned.
+            switchBuilder.addCase(fieldName, invokeVisit(traverseChildMtd, schemaField, fieldDef).retObject());
+        }
+
+        // Default option is to throw "NoSuchElementException(key)".
+        switchBuilder.defaultCase(new BytecodeBlock()
+            .append(newInstance(NoSuchElementException.class, keyVar))
+            .throwObject()
+        );
+
+        return switchBuilder.build();
+    }
+
+    /**
+     * Created switch bytecode block that invokes of construct methods for
+     *     {@link InnerNode#construct(String, ConfigurationSource, boolean)}.
+     *
+     * @param schemaFields Fields of the schema.
+     * @param fieldDefs Definitions for all fields in {@code schemaFields}.
+     * @param constructMtd Method definition {@link InnerNode#construct(String, ConfigurationSource, boolean)}
+     *      defined in {@code *Node} class.
+     * @return Created switch bytecode block that invokes of construct methods for fields.
+     */
+    private BytecodeNode treatSourceForConstruct(
+        Collection<Field> schemaFields,
+        Map<String, FieldDefinition> fieldDefs,
+        MethodDefinition constructMtd
+    ) {
+        Variable keyVar = constructMtd.getScope().getVariable("key");
+        Variable srcVar = constructMtd.getScope().getVariable("src");
+
+        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(constructMtd.getScope()).expression(keyVar);
+
+        for (Field schemaField : schemaFields) {
+            FieldDefinition fieldDef = fieldDefs.get(schemaField.getName());
+
+            BytecodeBlock caseClause = new BytecodeBlock();
+
+            switchBuilder.addCase(schemaField.getName(), caseClause);
+
+            // this.field = src == null ? null : src.unwrap(FieldType.class);
+            if (isValue(schemaField)) {
+                caseClause.append(constructMtd.getThis().setField(fieldDef, inlineIf(
+                    isNull(srcVar),
+                    constantNull(fieldDef.getType()),
+                    srcVar.invoke(UNWRAP, constantClass(fieldDef.getType())).cast(fieldDef.getType())
+                )));
+            }
+            // this.field = src == null ? null : src.descend(field = (field == null ? new FieldType() : field.copy()));
+            else if (isConfigValue(schemaField)) {
+                caseClause.append(new IfStatement()
+                    .condition(isNull(srcVar))
+                    .ifTrue(constructMtd.getThis().setField(fieldDef, constantNull(fieldDef.getType())))
+                    .ifFalse(new BytecodeBlock()
+                        .append(copyNodeField(constructMtd, fieldDef))
+                        .append(srcVar.invoke(DESCEND, constructMtd.getThis().getField(fieldDef)))
+                    )
+                );
+            }
+            // 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,
+                            constantString(namedCfgAnnotation.syntheticKeyName()),
+                            newNamedListElementLambda(fieldNodeClassName)
+                        )
+                    ))
+                    .ifFalse(new BytecodeBlock()
+                        .append(constructMtd.getThis().setField(
+                            fieldDef,
+                            constructMtd.getThis().getField(fieldDef).invoke(COPY).cast(fieldDef.getType())
+                        ))
+                        .append(srcVar.invoke(DESCEND, constructMtd.getThis().getField(fieldDef)))
+                    )
+                );
+            }
+        }
+
+        // Default option is to throw "NoSuchElementException(key)".
+        switchBuilder.defaultCase(new BytecodeBlock()
+            .append(newInstance(NoSuchElementException.class, keyVar))
+            .throwObject()
+        );
+
+        return switchBuilder.build();
+    }
+
+    /**
+     * Get interfaces for {@link InnerNode} definition for a configuration schema.
+     *
+     * @param schemaClass Configuration schema class.
+     * @param schemaExtensions Internal extensions of the configuration schema.
+     * @return Interfaces for {@link InnerNode} definition for a configuration schema.
+     */
+    private static ParameterizedType[] nodeClassInterfaces(Class<?> schemaClass, Set<Class<?>> schemaExtensions) {
+        return Stream.concat(Stream.of(schemaClass), schemaExtensions.stream())
+            .flatMap(cls -> Stream.of(viewClassName(cls), changeClassName(cls)))
+            .map(ParameterizedType::typeFromJavaClassName)
+            .toArray(ParameterizedType[]::new);
+    }
+
+    /**
+     * Get interfaces for {@link DynamicConfiguration} definition for a configuration schema.
+     *
+     * @param schemaClass Configuration schema class.
+     * @param schemaExtensions Internal extensions of the configuration schema.
+     * @return Interfaces for {@link DynamicConfiguration} definition for a configuration schema.
+     */
+    private static ParameterizedType[] configClassInterfaces(Class<?> schemaClass, Set<Class<?>> schemaExtensions) {
+        return Stream.concat(Stream.of(schemaClass), schemaExtensions.stream())
+            .map(cls -> typeFromJavaClassName(configurationClassName(cls)))
+            .toArray(ParameterizedType[]::new);
+    }
 }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/SchemaClassesInfo.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/SchemaClassesInfo.java
index 0a842e3..3568e47 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/SchemaClassesInfo.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/SchemaClassesInfo.java
@@ -24,6 +24,15 @@ import org.apache.ignite.internal.configuration.tree.InnerNode;
  * Class to cache compiled classes and hold precalculated names to reference other existing classes.
  */
 class SchemaClassesInfo {
+    /** Configuration class name postfix. */
+    private static final String CONFIGURATION_CLASS_POSTFIX = "Configuration";
+
+    /** View class name postfix. */
+    private static final String VIEW_CLASS_POSTFIX = "View";
+
+    /** Change class name postfix. */
+    private static final String CHANGE_CLASS_POSTFIX = "Change";
+
     /** Configuration Schema class. */
     public final Class<?> schemaClass;
 
@@ -49,19 +58,64 @@ class SchemaClassesInfo {
     public Class<? extends DynamicConfiguration<?, ?>> cfgImplClass;
 
     /**
+     * Constructor.
+     *
      * @param schemaClass Configuration Schema class instance.
      */
     SchemaClassesInfo(Class<?> schemaClass) {
         this.schemaClass = schemaClass;
-        String schemaClassName = schemaClass.getPackageName() + "." + schemaClass.getSimpleName(); // Support inner classes.
 
-        String prefix = schemaClassName.replaceAll("ConfigurationSchema$", "");
+        String prefix = prefix(schemaClass);
 
-        viewClassName = prefix + "View";
-        changeClassName = prefix + "Change";
-        cfgClassName = prefix + "Configuration";
+        viewClassName = prefix + VIEW_CLASS_POSTFIX;
+        changeClassName = prefix + CHANGE_CLASS_POSTFIX;
+        cfgClassName = prefix + CONFIGURATION_CLASS_POSTFIX;
 
         nodeClassName = prefix + "Node";
         cfgImplClassName = prefix + "ConfigurationImpl";
     }
+
+    /**
+     * Get the prefix for inner classes.
+     * <p/>
+     * Example: org.apache.ignite.NodeConfigurationSchema -> org.apache.ignite.Node
+     *
+     * @param schemaClass Configuration schema class.
+     * @return Prefix for inner classes.
+     */
+    static String prefix(Class<?> schemaClass) {
+        String schemaClassName = schemaClass.getPackageName() + "." + schemaClass.getSimpleName();
+
+        return schemaClassName.replaceAll("ConfigurationSchema$", "");
+    }
+
+    /**
+     * Get class name for the VIEW class.
+     *
+     * @param schemaClass Configuration schema class.
+     * @return Class name for the VIEW class.
+     */
+    static String viewClassName(Class<?> schemaClass) {
+        return prefix(schemaClass) + VIEW_CLASS_POSTFIX;
+    }
+
+    /**
+     * Get class name for the CHANGE class.
+     *
+     * @param schemaClass Configuration schema class.
+     * @return Class name for the CHANGE class.
+     */
+    static String changeClassName(Class<?> schemaClass) {
+        return prefix(schemaClass) + CHANGE_CLASS_POSTFIX;
+    }
+
+    /**
+     * Get class name for the Configuration class.
+     *
+     * @param schemaClass Configuration schema class.
+     * @return Class name for the Configuration class.
+     */
+    static String configurationClassName(Class<?> schemaClass) {
+        return prefix(schemaClass) + CONFIGURATION_CLASS_POSTFIX;
+    }
 }
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
index 5ce3bd2..c10995e 100644
--- 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
@@ -23,6 +23,7 @@ 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.apache.ignite.internal.configuration.tree.ConverterToMapVisitor;
 import org.jetbrains.annotations.NotNull;
 
 public class HoconConverter {
@@ -38,7 +39,7 @@ public class HoconConverter {
         ConfigurationRegistry registry,
         @NotNull List<String> path
     ) throws IllegalArgumentException {
-        Object res = registry.represent(path, new HoconConfigurationVisitor());
+        Object res = registry.represent(path, new ConverterToMapVisitor(false));
 
         return ConfigImpl.fromAnyRef(res, null);
     }
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 0deb215..00a93c8 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
@@ -131,7 +131,7 @@ class HoconListConfigurationSource implements ConfigurationSource {
 
             List<String> path = appendKey(this.path, syntheticKeyName);
 
-            node.construct(key, new HoconObjectConfigurationSource(syntheticKeyName, path, hoconCfg));
+            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 ce6f14c..ae8359c 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
@@ -83,14 +83,18 @@ class HoconObjectConfigurationSource implements ConfigurationSource {
             try {
                 switch (hoconCfgValue.valueType()) {
                     case NULL:
-                        node.construct(key, null);
+                        node.construct(key, null, false);
 
                         break;
 
                     case OBJECT: {
                         List<String> path = appendKey(this.path, key);
 
-                        node.construct(key, new HoconObjectConfigurationSource(null, path, (ConfigObject)hoconCfgValue));
+                        node.construct(
+                            key,
+                            new HoconObjectConfigurationSource(null, path, (ConfigObject)hoconCfgValue),
+                            false
+                        );
 
                         break;
                     }
@@ -98,7 +102,7 @@ class HoconObjectConfigurationSource implements ConfigurationSource {
                     case LIST: {
                         List<String> path = appendKey(this.path, key);
 
-                        node.construct(key, new HoconListConfigurationSource(path, (ConfigList)hoconCfgValue));
+                        node.construct(key, new HoconListConfigurationSource(path, (ConfigList)hoconCfgValue), false);
 
                         break;
                     }
@@ -106,7 +110,7 @@ class HoconObjectConfigurationSource implements ConfigurationSource {
                     default: {
                         List<String> path = appendKey(this.path, key);
 
-                        node.construct(key, new HoconPrimitiveConfigurationSource(path, hoconCfgValue));
+                        node.construct(key, new HoconPrimitiveConfigurationSource(path, hoconCfgValue), 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 cd59073..aa68f1e 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
@@ -27,9 +27,10 @@ public interface ConstructableTreeNode {
      *
      * @param key Field / named list element name to be constructed.
      * @param src Source that provides data for construction.
+     * @param includeInternal Include internal configuration nodes (private configuration extensions).
      * @throws NoSuchElementException If {@code key} cannot be constructed.
      */
-    void construct(String key, ConfigurationSource src) throws NoSuchElementException;
+    void construct(String key, ConfigurationSource src, boolean includeInternal) throws NoSuchElementException;
 
     /**
      * Public equivalent of {@link Object#clone()} method. Creates a copy with effectively the same content.
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/tree/ConverterToMapVisitor.java
similarity index 84%
rename from modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconConfigurationVisitor.java
rename to modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
index 7485792..5c45c64 100644
--- 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/tree/ConverterToMapVisitor.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.configuration.hocon;
+package org.apache.ignite.internal.configuration.tree;
 
 import java.io.Serializable;
 import java.lang.reflect.Array;
@@ -29,18 +29,26 @@ 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.
+ * {@link List} objects.
  */
-class HoconConfigurationVisitor implements ConfigurationVisitor<Object> {
+public class ConverterToMapVisitor implements ConfigurationVisitor<Object> {
+    /** Include internal configuration nodes (private configuration extensions). */
+    private final boolean includeInternal;
+
     /** Stack with intermediate results. Used to store values during recursive calls. */
-    private Deque<Object> deque = new ArrayDeque<>();
+    private final Deque<Object> deque = new ArrayDeque<>();
+
+    /**
+     * Constructor.
+     *
+     * @param includeInternal Include internal configuration nodes (private configuration extensions).
+     */
+    public ConverterToMapVisitor(boolean includeInternal) {
+        this.includeInternal = includeInternal;
+    }
 
     /** {@inheritDoc} */
     @Override public Object visitLeafNode(String key, Serializable val) {
@@ -62,7 +70,7 @@ class HoconConfigurationVisitor implements ConfigurationVisitor<Object> {
 
         deque.push(innerMap);
 
-        node.traverseChildren(this);
+        node.traverseChildren(this, includeInternal);
 
         deque.pop();
 
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/InnerNode.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/InnerNode.java
index 858fbf9..058112b 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/InnerNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/InnerNode.java
@@ -29,89 +29,127 @@ public abstract class InnerNode implements TraversableTreeNode, ConstructableTre
     /**
      * Method with auto-generated implementation. Must look like this:
      * <pre><code>
-     * {@literal @}Override public void traverseChildren(ConfigurationVisitor visitor) {
+     * {@literal @}Override public void traverseChildren(ConfigurationVisitor visitor, boolean includeInternal) {
      *     visitor.visitInnerNode("pojoField1", this.pojoField1);
      *
      *     visitor.visitNamedListNode("pojoField2", this.pojoField2);
      *
      *     visitor.visitLeafNode("primitiveField1", this.primitiveField1);
      *
-     *     visitor.visitLeafNode("primitiveField2", this.primitiveField2);
+     *     if (includeInternal) {
+     *          visitor.visitLeafNode("primitiveField2", this.primitiveField2);
+     *     }
      * }
      * </code></pre>
      *
      * Order of fields must be the same as they are described in configuration schema.
      *
      * @param visitor Configuration visitor.
+     * @param includeInternal Include internal configuration nodes (private configuration extensions).
      * @param <T> Parameter type of the passed visitor.
      */
-    public abstract <T> void traverseChildren(ConfigurationVisitor<T> visitor);
+    public abstract <T> void traverseChildren(ConfigurationVisitor<T> visitor, boolean includeInternal);
 
     /**
      * Method with auto-generated implementation. Must look like this:
      * <pre><code>
-     * {@literal @}Override public void traverseChild(String key, ConfigurationVisitor visitor) throws NoSuchElementException {
-     *     switch (key) {
-     *         case "pojoField1":
-     *             visitor.visitInnerNode("pojoField1", this.pojoField1);
-     *             break;
+     * {@literal @}Override public void traverseChild(String key, ConfigurationVisitor visitor, boolean includeInternal) throws NoSuchElementException {
+     *     if (boolean includeInternal) {
+     *         switch (key) {
+     *             case "pojoField1":
+     *                 visitor.visitInnerNode("pojoField1", this.pojoField1);
+     *                 break;
      *
-     *         case "pojoField2":
-     *             visitor.visitNamedListNode("pojoField2", this.pojoField2);
-     *             break;
+     *             case "pojoField2":
+     *                 visitor.visitNamedListNode("pojoField2", this.pojoField2);
+     *                 break;
      *
-     *         case "primitiveField1":
-     *             visitor.visitLeafNode("primitiveField1", this.primitiveField1);
-     *             break;
+     *             case "primitiveField1":
+     *                 visitor.visitLeafNode("primitiveField1", this.primitiveField1);
+     *                 break;
      *
-     *         case "primitiveField2":
-     *             visitor.visitLeafNode("primitiveField2", this.primitiveField2);
-     *             break;
+     *             case "primitiveField2":
+     *                 visitor.visitLeafNode("primitiveField2", this.primitiveField2);
+     *                 break;
      *
-     *         default:
-     *             throw new NoSuchElementException(key);
+     *             default:
+     *                 throw new NoSuchElementException(key);
+     *         }
+     *     }
+     *     else {
+     *         switch (key) {
+     *             case "primitiveField2":
+     *                  visitor.visitLeafNode("primitiveField2", this.primitiveField2);
+     *                  break;
+     *             default:
+     *                  throw new NoSuchElementException(key);
+     *         }
      *     }
      * }
      * </code></pre>
      *
      * @param key Name of the child.
      * @param visitor Configuration visitor.
+     * @param includeInternal Include internal configuration nodes (private configuration extensions).
      * @param <T> Parameter type of passed visitor.
      * @return Whatever {@code visitor} returned.
      * @throws NoSuchElementException If field {@code key} is not found.
      */
-    public abstract <T> T traverseChild(String key, ConfigurationVisitor<T> visitor) throws NoSuchElementException;
+    public abstract <T> T traverseChild(
+        String key,
+        ConfigurationVisitor<T> visitor,
+        boolean includeInternal
+    ) throws NoSuchElementException;
 
     /**
      * Method with auto-generated implementation. Must look like this:
      * <pre><code>
-     * {@literal @}Override public abstract void construct(String key, ConfigurationSource src) throws NoSuchElementException {
+     * {@literal @}Override public abstract void construct(String key, ConfigurationSource src, boolean includeInternal) throws NoSuchElementException {
+     *     if (includeInternal) {
+     *         switch (key) {
+     *              case "namedList":
+     *                  if (src == null)
+     *                      namedList = new NamedListNode&lt;&gt;(Foo::new);
+     *                  else
+     *                      src.descend(namedList = namedList.copy());
+     *                  break;
+     *
+     *              case "innerNode":
+     *                  if (src == null)
+     *                      innerNode = null;
+     *                  else
+     *                      src.descend(innerNode = (innerNode == null ? new Bar() : (Bar)innerNode.copy()));
+     *                  break;
+     *
+     *              case "leafInt":
+     *                  leafInt = src == null ? null : src.unwrap(Integer.class);
+     *                  break;
+     *
+     *              case "leafStr":
+     *                  leafStr = src == null ? null : src.unwrap(String.class);
+     *                  break;
+     *
+     *              default: throw new NoSuchElementException(key);
+     *         }
+     *     }
      *     switch (key) {
-     *         case "namedList":
-     *             if (src == null)
-     *                 namedList = new NamedListNode&lt;&gt;(Foo::new);
-     *             else
-     *                 src.descend(namedList = namedList.copy());
-     *             break;
-     *
-     *         case "innerNode":
-     *             if (src == null)
-     *                 innerNode = null;
-     *             else
-     *                 src.descend(innerNode = (innerNode == null ? new Bar() : (Bar)innerNode.copy()));
-     *             break;
-     *
-     *         case "leaf":
-     *             leaf = src == null ? null : src.unwrap(Integer.class);
-     *             break;
-     *
-     *         default: throw new NoSuchElementException(key);
+     *         switch (key) {
+     *              case "leafStr":
+     *                  leafStr = src == null ? null : src.unwrap(String.class);
+     *                  break;
+     *
+     *              default: throw new NoSuchElementException(key);
+     *         }
      *     }
      * }
      * </code></pre>
      * {@inheritDoc}
      */
-    @Override public abstract void construct(String key, ConfigurationSource src) throws NoSuchElementException;
+    @Override public abstract void construct(
+        String key,
+        ConfigurationSource src,
+        boolean includeInternal
+    ) throws NoSuchElementException;
 
     /**
      * Assigns default value to the corresponding leaf. Defaults are gathered from configuration schema class.
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 d6b97ee..342673d 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
@@ -336,7 +336,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
     }
 
     /** {@inheritDoc} */
-    @Override public void construct(String key, ConfigurationSource src) {
+    @Override public void construct(String key, ConfigurationSource src, boolean includeInternal) {
         if (src == null)
             delete(key);
         else
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
index a9f2739..c8e359d 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
@@ -49,7 +49,7 @@ public class ConfigurationFlattener {
 
         // Explicit access to the children of super root guarantees that "oldInnerNodesStack" is never empty and thus
         // we don't need null-checks when calling Deque#peek().
-        updates.traverseChildren(new FlattenerVisitor(oldInnerNodesStack, resMap));
+        updates.traverseChildren(new FlattenerVisitor(oldInnerNodesStack, resMap), true);
 
         assert oldInnerNodesStack.peek() == curRoot : oldInnerNodesStack;
 
@@ -100,7 +100,7 @@ public class ConfigurationFlattener {
         /** {@inheritDoc} */
         @Override public Void doVisitLeafNode(String key, Serializable newVal) {
             // Read same value from old tree.
-            Serializable oldVal = oldInnerNodesStack.peek().traverseChild(key, ConfigurationUtil.leafNodeVisitor());
+            Serializable oldVal = oldInnerNodesStack.peek().traverseChild(key, ConfigurationUtil.leafNodeVisitor(), true);
 
             // Do not put duplicates into the resulting map.
             if (singleTreeTraversal || !Objects.deepEquals(oldVal, newVal))
@@ -112,7 +112,7 @@ public class ConfigurationFlattener {
         /** {@inheritDoc} */
         @Override public Void doVisitInnerNode(String key, InnerNode newNode) {
             // Read same node from old tree.
-            InnerNode oldNode = oldInnerNodesStack.peek().traverseChild(key, ConfigurationUtil.innerNodeVisitor());
+            InnerNode oldNode = oldInnerNodesStack.peek().traverseChild(key, ConfigurationUtil.innerNodeVisitor(), true);
 
             // Skip subtree that has not changed.
             if (oldNode == newNode && !singleTreeTraversal)
@@ -123,7 +123,7 @@ public class ConfigurationFlattener {
             else {
                 oldInnerNodesStack.push(oldNode);
 
-                newNode.traverseChildren(this);
+                newNode.traverseChildren(this, true);
 
                 oldInnerNodesStack.pop();
             }
@@ -134,7 +134,7 @@ public class ConfigurationFlattener {
         /** {@inheritDoc} */
         @Override public <N extends InnerNode> Void doVisitNamedListNode(String key, NamedListNode<N> newNode) {
             // Read same named list node from old tree.
-            NamedListNode<?> oldNode = oldInnerNodesStack.peek().traverseChild(key, ConfigurationUtil.namedListNodeVisitor());
+            NamedListNode<?> oldNode = oldInnerNodesStack.peek().traverseChild(key, ConfigurationUtil.namedListNodeVisitor(), true);
 
             // Skip subtree that has not changed.
             if (oldNode == newNode && !singleTreeTraversal)
@@ -171,7 +171,7 @@ public class ConfigurationFlattener {
                         else {
                             oldInnerNodesStack.push(oldNamedElement);
 
-                            newNamedElement.traverseChildren(this);
+                            newNamedElement.traverseChildren(this, true);
 
                             oldInnerNodesStack.pop();
                         }
@@ -215,7 +215,7 @@ public class ConfigurationFlattener {
             singleTreeTraversal = true;
             deletion = delete;
 
-            node.traverseChildren(this);
+            node.traverseChildren(this, true);
 
             deletion = false;
             singleTreeTraversal = false;
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationNotificationsUtil.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationNotificationsUtil.java
index 02a58b0..332c94b 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationNotificationsUtil.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationNotificationsUtil.java
@@ -78,7 +78,7 @@ public class ConfigurationNotificationsUtil {
         oldInnerNode.traverseChildren(new ConfigurationVisitor<Void>() {
             /** {@inheritDoc} */
             @Override public Void visitLeafNode(String key, Serializable oldLeaf) {
-                Serializable newLeaf = newInnerNode.traverseChild(key, leafNodeVisitor());
+                Serializable newLeaf = newInnerNode.traverseChild(key, leafNodeVisitor(), true);
 
                 if (newLeaf != oldLeaf) {
                     var dynProperty = (DynamicProperty<Serializable>)cfgNodeMembers.get(key);
@@ -98,7 +98,7 @@ public class ConfigurationNotificationsUtil {
 
             /** {@inheritDoc} */
             @Override public Void visitInnerNode(String key, InnerNode oldNode) {
-                InnerNode newNode = newInnerNode.traverseChild(key, innerNodeVisitor());
+                InnerNode newNode = newInnerNode.traverseChild(key, innerNodeVisitor(), true);
 
                 var dynCfg = (DynamicConfiguration<InnerNode, ?>)cfgNodeMembers.get(key);
 
@@ -109,7 +109,7 @@ public class ConfigurationNotificationsUtil {
 
             /** {@inheritDoc} */
             @Override public <N extends InnerNode> Void visitNamedListNode(String key, NamedListNode<N> oldNamedList) {
-                var newNamedList = (NamedListNode<InnerNode>)newInnerNode.traverseChild(key, namedListNodeVisitor());
+                var newNamedList = (NamedListNode<InnerNode>)newInnerNode.traverseChild(key, namedListNodeVisitor(), true);
 
                 if (newNamedList != oldNamedList) {
                     var namedListCfg = (NamedListConfiguration<?, InnerNode, ?>)cfgNodeMembers.get(key);
@@ -226,7 +226,7 @@ public class ConfigurationNotificationsUtil {
 
                 return null;
             }
-        });
+        }, true);
     }
 
     /**
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 8c1cdb5..e093f12 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
@@ -18,17 +18,29 @@
 package org.apache.ignite.internal.configuration.util;
 
 import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Queue;
 import java.util.RandomAccess;
+import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 import org.apache.ignite.configuration.NamedListView;
 import org.apache.ignite.configuration.RootKey;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
 import org.apache.ignite.internal.configuration.tree.ConfigurationVisitor;
@@ -37,6 +49,8 @@ import org.apache.ignite.internal.configuration.tree.InnerNode;
 import org.apache.ignite.internal.configuration.tree.NamedListNode;
 import org.apache.ignite.internal.configuration.tree.TraversableTreeNode;
 
+import static java.util.stream.Collectors.toList;
+
 /** */
 public class ConfigurationUtil {
     /** Configuration source that copies values without modifying tham. */
@@ -96,23 +110,30 @@ public class ConfigurationUtil {
      *
      * @param keys Random access list with keys.
      * @param node Node where method will search for subnode.
+     * @param includeInternal Include internal configuration nodes (private configuration extensions).
      * @return Either {@link TraversableTreeNode} or {@link Serializable} depending on the keys and schema.
      * @throws KeyNotFoundException If node is not found.
      */
-    public static Object find(List<String> keys, TraversableTreeNode node) throws KeyNotFoundException {
+    public static Object find(
+        List<String> keys,
+        TraversableTreeNode node,
+        boolean includeInternal
+    ) throws KeyNotFoundException {
         assert keys instanceof RandomAccess : keys.getClass();
 
         var visitor = new ConfigurationVisitor<>() {
-            /** */
+            /** Current index of the key in the {@code keys}. */
             private int i;
 
+            /** {@inheritDoc} */
             @Override public Object visitLeafNode(String key, Serializable val) {
                 if (i != keys.size())
                     throw new KeyNotFoundException("Configuration value '" + join(keys.subList(0, i)) + "' is a leaf");
-
-                return val;
+                else
+                    return val;
             }
 
+            /** {@inheritDoc} */
             @Override public Object visitInnerNode(String key, InnerNode node) {
                 if (i == keys.size())
                     return node;
@@ -120,14 +141,17 @@ public class ConfigurationUtil {
                     throw new KeyNotFoundException("Configuration node '" + join(keys.subList(0, i)) + "' is null");
                 else {
                     try {
-                        return node.traverseChild(keys.get(i++), this);
+                        return node.traverseChild(keys.get(i++), this, includeInternal);
                     }
                     catch (NoSuchElementException e) {
-                        throw new KeyNotFoundException("Configuration '" + join(keys.subList(0, i)) + "' is not found");
+                        throw new KeyNotFoundException(
+                            "Configuration '" + join(keys.subList(0, i)) + "' is not found"
+                        );
                     }
                 }
             }
 
+            /** {@inheritDoc} */
             @Override public <N extends InnerNode> Object visitNamedListNode(String key, NamedListNode<N> node) {
                 if (i == keys.size())
                     return node;
@@ -272,13 +296,13 @@ public class ConfigurationUtil {
                         continue;
 
                     if (val == null)
-                        node.construct(key, null);
+                        node.construct(key, null, true);
                     else if (val instanceof Map)
-                        node.construct(key, new InnerConfigurationSource((Map<String, ?>)val));
+                        node.construct(key, new InnerConfigurationSource((Map<String, ?>)val), true);
                     else {
                         assert val instanceof Serializable;
 
-                        node.construct(key, new LeafConfigurationSource((Serializable)val));
+                        node.construct(key, new LeafConfigurationSource((Serializable)val), true);
                     }
                 }
             }
@@ -326,7 +350,7 @@ public class ConfigurationUtil {
                         boolean construct = map.size() != sizeDiff;
 
                         if (oldKey == null) {
-                            node.construct(newKey, new InnerConfigurationSource(map));
+                            node.construct(newKey, new InnerConfigurationSource(map), true);
 
                             node.setInternalId(newKey, internalId);
                         }
@@ -334,10 +358,10 @@ public class ConfigurationUtil {
                             node.rename(oldKey, newKey);
 
                             if (construct)
-                                node.construct(newKey, new InnerConfigurationSource(map));
+                                node.construct(newKey, new InnerConfigurationSource(map), true);
                         }
                         else if (construct)
-                            node.construct(oldKey, new InnerConfigurationSource(map));
+                            node.construct(oldKey, new InnerConfigurationSource(map), true);
                         // Else it's just index adjustment after new elements insertion.
 
                         if (newKey == null)
@@ -366,7 +390,7 @@ public class ConfigurationUtil {
                     else {
                         assert val instanceof Serializable;
 
-                        node.construct(oldKey, new LeafConfigurationSource((Serializable)val));
+                        node.construct(oldKey, new LeafConfigurationSource((Serializable)val), true);
                     }
                 }
 
@@ -407,6 +431,7 @@ public class ConfigurationUtil {
      */
     public static void addDefaults(InnerNode node) {
         node.traverseChildren(new ConfigurationVisitor<>() {
+            /** {@inheritDoc} */
             @Override public Object visitLeafNode(String key, Serializable val) {
                 // If source value is null then inititalise the same value on the destination node.
                 if (val == null)
@@ -415,25 +440,27 @@ public class ConfigurationUtil {
                 return null;
             }
 
+            /** {@inheritDoc} */
             @Override public Object visitInnerNode(String key, InnerNode innerNode) {
                 // Instantiate field in destination node before doing something else or copy it if it wasn't null.
-                node.construct(key, EMPTY_CFG_SRC);
+                node.construct(key, EMPTY_CFG_SRC, true);
 
-                addDefaults(node.traverseChild(key, innerNodeVisitor()));
+                addDefaults(node.traverseChild(key, innerNodeVisitor(), true));
 
                 return null;
             }
 
+            /** {@inheritDoc} */
             @Override public <N extends InnerNode> Object visitNamedListNode(String key, NamedListNode<N> namedList) {
                 // Copy internal map.
-                node.construct(key, EMPTY_CFG_SRC);
+                node.construct(key, EMPTY_CFG_SRC, true);
 
-                namedList = (NamedListNode<N>)node.traverseChild(key, namedListNodeVisitor());
+                namedList = (NamedListNode<N>)node.traverseChild(key, namedListNodeVisitor(), true);
 
                 for (String namedListKey : namedList.namedListKeys()) {
                     if (namedList.get(namedListKey) != null) {
                         // Copy the element.
-                        namedList.construct(namedListKey, EMPTY_CFG_SRC);
+                        namedList.construct(namedListKey, EMPTY_CFG_SRC, true);
 
                         addDefaults(namedList.get(namedListKey));
                     }
@@ -441,7 +468,7 @@ public class ConfigurationUtil {
 
                 return null;
             }
-        });
+        }, true);
     }
 
     /**
@@ -469,7 +496,7 @@ public class ConfigurationUtil {
 
                 return null;
             }
-        });
+        }, true);
     }
 
     /**
@@ -499,8 +526,8 @@ public class ConfigurationUtil {
      */
     public static ConfigurationVisitor<NamedListNode<?>> namedListNodeVisitor() {
         return new ConfigurationVisitor<>() {
-            @Override
-            public <N extends InnerNode> NamedListNode<?> visitNamedListNode(String key, NamedListNode<N> node) {
+            /** {@inheritDoc} */
+            @Override public <N extends InnerNode> NamedListNode<?> visitNamedListNode(String key, NamedListNode<N> node) {
                 return node;
             }
         };
@@ -519,4 +546,196 @@ public class ConfigurationUtil {
             }
         }
     }
+
+    /**
+     * Get and check schemas and their extensions.
+     *
+     * @param extensions Schema extensions with {@link InternalConfiguration}.
+     * @return Internal schema extensions. Mapping: original of the scheme -> internal extensions.
+     * @throws IllegalArgumentException If the schema or its extensions are not valid.
+     */
+    public static Map<Class<?>, Set<Class<?>>> internalSchemaExtensions(Collection<Class<?>> extensions) {
+        if (extensions.isEmpty())
+            return Map.of();
+        else {
+            Map<Class<?>, Set<Class<?>>> res = new HashMap<>();
+
+            for (Class<?> extension : extensions) {
+                if (!extension.isAnnotationPresent(InternalConfiguration.class)) {
+                    throw new IllegalArgumentException(String.format(
+                        "Extension should contain @%s: %s",
+                        InternalConfiguration.class.getSimpleName(),
+                        extension.getName()
+                    ));
+                }
+                else
+                    res.computeIfAbsent(extension.getSuperclass(), cls -> new HashSet<>()).add(extension);
+            }
+
+            return res;
+        }
+    }
+
+    /**
+     * Checks whether configuration schema field represents primitive configuration value.
+     *
+     * @param schemaField Configuration Schema class field.
+     * @return {@code true} if field represents primitive configuration.
+     */
+    public static boolean isValue(Field schemaField) {
+        return schemaField.isAnnotationPresent(Value.class);
+    }
+
+    /**
+     * Checks whether configuration schema field represents regular configuration value.
+     *
+     * @param schemaField Configuration Schema class field.
+     * @return {@code true} if field represents regular configuration.
+     */
+    public static boolean isConfigValue(Field schemaField) {
+        return schemaField.isAnnotationPresent(ConfigValue.class);
+    }
+
+    /**
+     * Checks whether configuration schema field represents named list configuration value.
+     *
+     * @param schemaField Configuration Schema class field.
+     * @return {@code true} if field represents named list configuration.
+     */
+    public static boolean isNamedConfigValue(Field schemaField) {
+        return schemaField.isAnnotationPresent(NamedConfigValue.class);
+    }
+
+    /**
+     * Get the value of a {@link NamedConfigValue#syntheticKeyName}.
+     *
+     * @param field Configuration Schema class field.
+     * @return Name for the synthetic key.
+     */
+    public static String syntheticKeyName(Field field) {
+        assert isNamedConfigValue(field) : field;
+
+        return field.getAnnotation(NamedConfigValue.class).syntheticKeyName();
+    }
+
+    /**
+     * Get the value of a {@link Value#hasDefault}.
+     *
+     * @param field Configuration Schema class field.
+     * @return Indicates that the current configuration value has a default value.
+     */
+    public static boolean hasDefault(Field field) {
+        assert isValue(field) : field;
+
+        return field.getAnnotation(Value.class).hasDefault();
+    }
+
+    /**
+     * Get the default value of a {@link Value}.
+     *
+     * @param field Configuration Schema class field.
+     * @return Default value.
+     */
+    public static Object defaultValue(Field field) {
+        assert hasDefault(field) : field;
+
+        try {
+            Object o = field.getDeclaringClass().getDeclaredConstructor().newInstance();
+
+            field.setAccessible(true);
+
+            return field.get(o);
+        }
+        catch (ReflectiveOperationException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Get merged schema extension fields.
+     *
+     * @param extensions Configuration schema extensions ({@link InternalConfiguration}).
+     * @return Unique fields of the schema and its extensions.
+     * @throws IllegalArgumentException If there is a conflict in field names.
+     */
+    public static Set<Field> extensionsFields(Collection<Class<?>> extensions) {
+        if (extensions.isEmpty())
+            return Set.of();
+        else {
+            Map<String, Field> res = new HashMap<>();
+
+            for (Class<?> extension : extensions) {
+                assert extension.isAnnotationPresent(InternalConfiguration.class) : extension;
+
+                for (Field field : extension.getDeclaredFields()) {
+                    String fieldName = field.getName();
+
+                    if (res.containsKey(fieldName)) {
+                        throw new IllegalArgumentException(String.format(
+                            "Duplicate field names are not allowed [field=%s, classes=%s]",
+                            field,
+                            classNames(res.get(fieldName), field)
+                        ));
+                    }
+                    else
+                        res.put(fieldName, field);
+                }
+            }
+
+            return Set.copyOf(res.values());
+        }
+    }
+
+    /**
+     * Collect all configuration schemes with {@link ConfigurationRoot} or {@link Config}
+     * including all sub configuration schemes for fields with {@link ConfigValue} or {@link NamedConfigValue}.
+     *
+     * @param schemaClasses Configuration schemas (starting points) with {@link ConfigurationRoot} or {@link Config}.
+     * @return All configuration schemes with {@link ConfigurationRoot} or {@link Config}.
+     * @throws IllegalArgumentException If the configuration schemas does not contain
+     *      {@link ConfigurationRoot} or {@link Config}.
+     */
+    public static Set<Class<?>> collectSchemas(Collection<Class<?>> schemaClasses) {
+        if (schemaClasses.isEmpty())
+            return Set.of();
+        else {
+            Set<Class<?>> res = new HashSet<>();
+
+            Queue<Class<?>> queue = new ArrayDeque<>(Set.copyOf(schemaClasses));
+
+            while (!queue.isEmpty()) {
+                Class<?> cls = queue.poll();
+
+                if (!cls.isAnnotationPresent(ConfigurationRoot.class) && !cls.isAnnotationPresent(Config.class)) {
+                    throw new IllegalArgumentException(String.format(
+                        "Configuration schema must contain @%s or @%s: %s",
+                        ConfigurationRoot.class.getSimpleName(),
+                        Config.class.getSimpleName(),
+                        cls.getName()
+                    ));
+                }
+                else {
+                    res.add(cls);
+
+                    for (Field f : cls.getDeclaredFields()) {
+                        if ((f.isAnnotationPresent(ConfigValue.class) || f.isAnnotationPresent(NamedConfigValue.class))
+                            && !res.contains(f.getType()))
+                            queue.add(f.getType());
+                    }
+                }
+            }
+
+            return res;
+        }
+    }
+
+    /**
+     * Get the class names of the fields.
+     *
+     * @param fields Fields.
+     * @return Fields class names.
+     */
+    public static List<String> classNames(Field... fields) {
+        return Stream.of(fields).map(Field::getDeclaringClass).map(Class::getName).collect(toList());
+    }
 }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/KeysTrackingConfigurationVisitor.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/KeysTrackingConfigurationVisitor.java
index a2608f9..9b9a16f 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/KeysTrackingConfigurationVisitor.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/KeysTrackingConfigurationVisitor.java
@@ -89,7 +89,7 @@ public abstract class KeysTrackingConfigurationVisitor<T> implements Configurati
      * @return Anything that implementation decides to return.
      */
     protected T doVisitInnerNode(String key, InnerNode node) {
-        node.traverseChildren(this);
+        node.traverseChildren(this, true);
 
         return null;
     }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/validation/ValidationContextImpl.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/validation/ValidationContextImpl.java
index e8f5127..171b90b 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/validation/ValidationContextImpl.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/validation/ValidationContextImpl.java
@@ -96,7 +96,7 @@ class ValidationContextImpl<VIEW> implements ValidationContext<VIEW> {
     /** {@inheritDoc} */
     @Override public VIEW getOldValue() {
         try {
-            return (VIEW)find(currentPath, oldRoots);
+            return (VIEW)find(currentPath, oldRoots, true);
         }
         catch (KeyNotFoundException ignore) {
             return null;
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/validation/ValidationUtil.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/validation/ValidationUtil.java
index 0af0765..ccb1bb1 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/validation/ValidationUtil.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/validation/ValidationUtil.java
@@ -70,7 +70,7 @@ public class ValidationUtil {
 
                         return null;
                     }
-                });
+                }, true);
 
                 return super.doVisitInnerNode(key, innerNode);
             }
@@ -135,7 +135,7 @@ public class ValidationUtil {
                     }
                 }
             }
-        });
+        }, true);
 
         return issues;
     }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/CollectionUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/CollectionUtils.java
index 21a7899..7a1bf4b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/CollectionUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/CollectionUtils.java
@@ -18,10 +18,19 @@
 package org.apache.ignite.internal.util;
 
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
 import org.jetbrains.annotations.Nullable;
 
+import static java.util.Collections.addAll;
+import static java.util.Collections.emptyIterator;
+import static java.util.Collections.unmodifiableSet;
+
 /**
  * Utility class provides various method to work with collections.
  */
@@ -70,6 +79,69 @@ public final class CollectionUtils {
         return list.get(0);
     }
 
+    /**
+     * Union set and items.
+     *
+     * @param set Set.
+     * @param ts Items.
+     * @param <T> Type of the elements of the list.
+     * @return Immutable union of set and items.
+     */
+    @SafeVarargs
+    public static <T> Set<T> union(@Nullable Set<T> set, @Nullable T... ts) {
+        if (nullOrEmpty(set))
+            return ts == null || ts.length == 0 ? Set.of() : Set.of(ts);
+        else if (ts == null || ts.length == 0)
+            return unmodifiableSet(set);
+        else {
+            Set<T> res = new HashSet<>(set);
+
+            addAll(res, ts);
+
+            return unmodifiableSet(res);
+        }
+    }
+
+    /**
+     * Create a lazy concatenation of iterables.
+     * <p>
+     * NOTE: {@link Iterator#remove} - not supported.
+     *
+     * @param iterables Iterables.
+     * @param <T> Type of the elements.
+     * @return Concatenation of iterables.
+     */
+    @SafeVarargs
+    public static <T> Iterable<? extends T> concat(@Nullable Iterable<? extends T>... iterables) {
+        if (iterables == null || iterables.length == 0)
+            return Collections::emptyIterator;
+        else {
+            return () -> new Iterator<T>() {
+                /** Current index at {@code iterables}. */
+                int i = 0;
+
+                /** Current iterator. */
+                Iterator<? extends T> curr = emptyIterator();
+
+                /** {@inheritDoc} */
+                @Override public boolean hasNext() {
+                    while (!curr.hasNext() && i < iterables.length)
+                        curr = iterables[i++].iterator();
+
+                    return curr.hasNext();
+                }
+
+                /** {@inheritDoc} */
+                @Override public T next() {
+                    if (!hasNext())
+                        throw new NoSuchElementException();
+                    else
+                        return curr.next();
+                }
+            };
+        }
+    }
+
     /** Stub. */
     private CollectionUtils() {
         // No op.
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/CollectionUtilsTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/CollectionUtilsTest.java
new file mode 100644
index 0000000..27acfa0
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/util/CollectionUtilsTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.util;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.StreamSupport;
+import org.junit.jupiter.api.Test;
+
+import static java.util.stream.Collectors.toList;
+import static org.apache.ignite.internal.util.CollectionUtils.concat;
+import static org.apache.ignite.internal.util.CollectionUtils.union;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Testing the {@link CollectionUtils}.
+ */
+public class CollectionUtilsTest {
+    /** */
+    @Test
+    void testConcatIterables() {
+        assertTrue(collect(concat(null)).isEmpty());
+        assertTrue(collect(concat(List.of())).isEmpty());
+        assertTrue(collect(concat(List.of(), List.of())).isEmpty());
+
+        assertEquals(List.of(1), collect(concat(List.of(1))));
+        assertEquals(List.of(1), collect(concat(List.of(1), List.of())));
+        assertEquals(List.of(1), collect(concat(List.of(), List.of(1))));
+
+        assertEquals(List.of(1, 2, 3), collect(concat(List.of(1), List.of(2, 3))));
+    }
+
+    /** */
+    @Test
+    void testUnion() {
+        assertTrue(union(null, null).isEmpty());
+        assertTrue(union(Set.of(), null).isEmpty());
+        assertTrue(union(null, new Object[] {}).isEmpty());
+
+        assertEquals(Set.of(1), union(Set.of(1), null));
+        assertEquals(Set.of(1), union(Set.of(1), new Integer[] {}));
+        assertEquals(Set.of(1), union(null, new Integer[] {1}));
+        assertEquals(Set.of(1), union(Set.of(), new Integer[] {1}));
+
+        assertEquals(Set.of(1, 2), union(Set.of(1), new Integer[] {2}));
+    }
+
+    /**
+     * Collect of elements.
+     *
+     * @param iterable Iterable.
+     * @param <T> Type of the elements.
+     * @return Collected elements.
+     */
+    private <T> List<? extends T> collect(Iterable<? extends T> iterable) {
+        return StreamSupport.stream(iterable.spliterator(), false).collect(toList());
+    }
+}
diff --git a/modules/network/src/integrationTest/java/org/apache/ignite/utils/ClusterServiceTestUtils.java b/modules/network/src/integrationTest/java/org/apache/ignite/utils/ClusterServiceTestUtils.java
index 2767b0a..93ca0e3 100644
--- a/modules/network/src/integrationTest/java/org/apache/ignite/utils/ClusterServiceTestUtils.java
+++ b/modules/network/src/integrationTest/java/org/apache/ignite/utils/ClusterServiceTestUtils.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.utils;
 
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import org.apache.ignite.configuration.annotation.ConfigurationType;
 import org.apache.ignite.configuration.schemas.network.NetworkConfiguration;
@@ -59,7 +60,8 @@ public class ClusterServiceTestUtils {
         ConfigurationManager nodeConfigurationMgr = new ConfigurationManager(
             Collections.singleton(NetworkConfiguration.KEY),
             Map.of(),
-            new TestConfigurationStorage(ConfigurationType.LOCAL)
+            new TestConfigurationStorage(ConfigurationType.LOCAL),
+            List.of()
         );
 
         var clusterSvc = clusterSvcFactory.createClusterService(
diff --git a/modules/rest/src/test/java/org/apache/ignite/rest/presentation/ConfigurationPresentationTest.java b/modules/rest/src/test/java/org/apache/ignite/rest/presentation/ConfigurationPresentationTest.java
index d54669e..6c35c8f 100644
--- a/modules/rest/src/test/java/org/apache/ignite/rest/presentation/ConfigurationPresentationTest.java
+++ b/modules/rest/src/test/java/org/apache/ignite/rest/presentation/ConfigurationPresentationTest.java
@@ -70,7 +70,8 @@ public class ConfigurationPresentationTest {
         cfgRegistry = new ConfigurationRegistry(
             List.of(TestRootConfiguration.KEY),
             Map.of(Value.class, Set.of(validator)),
-            new TestConfigurationStorage(LOCAL)
+            new TestConfigurationStorage(LOCAL),
+            List.of()
         );
 
         cfgRegistry.start();
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/storage/ITDistributedConfigurationStorageTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/storage/ITDistributedConfigurationStorageTest.java
index cbc070c..7ed2695 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/storage/ITDistributedConfigurationStorageTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/storage/ITDistributedConfigurationStorageTest.java
@@ -101,7 +101,8 @@ public class ITDistributedConfigurationStorageTest {
             cfgManager = new ConfigurationManager(
                 rootKeys,
                 Map.of(),
-                new LocalConfigurationStorage(vaultManager)
+                new LocalConfigurationStorage(vaultManager),
+                List.of()
             );
 
             metaStorageManager = new MetaStorageManager(
diff --git a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
index 9c2755f..0aa136c 100644
--- a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
+++ b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
@@ -150,7 +150,8 @@ public class IgniteImpl implements Ignite {
                 ClientConnectorConfiguration.KEY
             ),
             Map.of(),
-            new LocalConfigurationStorage(vaultMgr)
+            new LocalConfigurationStorage(vaultMgr),
+            List.of()
         );
 
         clusterSvc = new ScaleCubeClusterServiceFactory().createClusterService(
@@ -178,7 +179,8 @@ public class IgniteImpl implements Ignite {
                 TablesConfiguration.KEY
             ),
             Map.of(),
-            new DistributedConfigurationStorage(metaStorageMgr, vaultMgr)
+            new DistributedConfigurationStorage(metaStorageMgr, vaultMgr),
+            List.of()
         );
 
         baselineMgr = new BaselineManager(
diff --git a/modules/schema/src/test/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverterTest.java b/modules/schema/src/test/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverterTest.java
index 4ec9da5..f92ee5b 100644
--- a/modules/schema/src/test/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverterTest.java
+++ b/modules/schema/src/test/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverterTest.java
@@ -73,7 +73,8 @@ public class SchemaConfigurationConverterTest {
         confRegistry = new ConfigurationRegistry(
             List.of(TablesConfiguration.KEY),
             Map.of(TableValidator.class, Set.of(SchemaTableValidatorImpl.INSTANCE)),
-            new TestConfigurationStorage(DISTRIBUTED)
+            new TestConfigurationStorage(DISTRIBUTED),
+            List.of()
         );
 
         confRegistry.start();
diff --git a/modules/table/src/test/java/org/apache/ignite/internal/table/TableManagerTest.java b/modules/table/src/test/java/org/apache/ignite/internal/table/TableManagerTest.java
index d5fa621..c2e79b6 100644
--- a/modules/table/src/test/java/org/apache/ignite/internal/table/TableManagerTest.java
+++ b/modules/table/src/test/java/org/apache/ignite/internal/table/TableManagerTest.java
@@ -160,13 +160,15 @@ public class TableManagerTest {
             nodeCfgMgr = new ConfigurationManager(
                 List.of(NodeConfiguration.KEY),
                 Map.of(),
-                new TestConfigurationStorage(LOCAL)
+                new TestConfigurationStorage(LOCAL),
+                List.of()
             );
 
             clusterCfgMgr = new ConfigurationManager(
                 List.of(ClusterConfiguration.KEY, TablesConfiguration.KEY),
                 Map.of(),
-                new TestConfigurationStorage(DISTRIBUTED)
+                new TestConfigurationStorage(DISTRIBUTED),
+                List.of()
             );
 
             nodeCfgMgr.start();