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/11/01 09:17:10 UTC

[ignite-3] branch main updated: IGNITE-14645 Support polymorphic configuration nodes. (#366)

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 445f455  IGNITE-14645 Support polymorphic configuration nodes. (#366)
445f455 is described below

commit 445f45591ddeaf541140c4c5b2d89f72cd387d08
Author: Kirill Tkalenko <tk...@yandex.ru>
AuthorDate: Mon Nov 1 12:15:32 2021 +0300

    IGNITE-14645 Support polymorphic configuration nodes. (#366)
---
 .../facebook/presto/bytecode/ClassGenerator.java   |   15 +-
 .../ignite/client/handler/ITClientHandlerTest.java |    1 +
 .../apache/ignite/client/AbstractClientTest.java   |    1 +
 .../configuration/processor/ITProcessorTest.java   |  113 +-
 .../ErrorPolymorphic0ConfigurationSchema.java      |   29 +
 .../ErrorPolymorphic1ConfigurationSchema.java      |   29 +
 .../ErrorPolymorphic2ConfigurationSchema.java      |   29 +
 .../ErrorPolymorphic3ConfigurationSchema.java      |   29 +
 .../ErrorPolymorphic4ConfigurationSchema.java      |   28 +
 .../ErrorPolymorphic5ConfigurationSchema.java      |   29 +
 .../ErrorPolymorphic6ConfigurationSchema.java      |   31 +
 .../ErrorPolymorphic7ConfigurationSchema.java      |   35 +
 .../ErrorPolymorphic8ConfigurationSchema.java      |   36 +
 ...rorPolymorphicInstance0ConfigurationSchema.java |   29 +
 ...rorPolymorphicInstance1ConfigurationSchema.java |   29 +
 ...rorPolymorphicInstance2ConfigurationSchema.java |   29 +
 ...rorPolymorphicInstance3ConfigurationSchema.java |   27 +
 ...rorPolymorphicInstance4ConfigurationSchema.java |   29 +
 ...rorPolymorphicInstance5ConfigurationSchema.java |   33 +
 ...rorPolymorphicInstance6ConfigurationSchema.java |   31 +
 .../polymorphic/SimpleConfigurationSchema.java     |   36 +
 .../SimplePolymorphicConfigurationSchema.java      |   36 +
 ...mplePolymorphicInstanceConfigurationSchema.java |   36 +
 .../polymorphic/SimpleRootConfigurationSchema.java |   36 +
 .../configuration/processor/Processor.java         |  595 ++++--
 .../internal/configuration/processor/Utils.java    |   26 +
 .../configuration/processor/UtilsTest.java         |   44 +
 .../ConfigurationReadOnlyException.java            |   32 +
 ...nfigurationWrongPolymorphicTypeIdException.java |   32 +
 .../ignite/configuration/NamedListChange.java      |   18 +-
 .../apache/ignite/configuration/NamedListView.java |    8 +-
 .../ignite/configuration/PolymorphicChange.java    |   28 +
 .../annotation/PolymorphicConfig.java              |   40 +
 .../annotation/PolymorphicConfigInstance.java      |   43 +
 .../configuration/annotation/PolymorphicId.java    |   43 +
 .../configuration/ConfigurationChanger.java        |   21 +-
 .../configuration/ConfigurationManager.java        |   14 +-
 .../internal/configuration/ConfigurationNode.java  |    6 +-
 .../configuration/ConfigurationRegistry.java       |  143 +-
 .../configuration/ConfigurationTreeWrapper.java    |   68 +
 .../DirectConfigurationTreeWrapper.java            |   46 +
 .../configuration/DirectDynamicProperty.java       |    6 +-
 .../configuration/DynamicConfiguration.java        |   96 +-
 .../internal/configuration/DynamicProperty.java    |   13 +-
 .../configuration/NamedListConfiguration.java      |   15 +-
 .../asm/ConfigurationAsmGenerator.java             | 2237 ++++++++++++++++----
 .../hocon/HoconObjectConfigurationSource.java      |    6 +
 .../configuration/tree/ConfigurationSource.java    |   16 +-
 .../configuration/tree/ConstructableTreeNode.java  |    4 +-
 .../configuration/tree/ConverterToMapVisitor.java  |    2 +-
 .../internal/configuration/tree/InnerNode.java     |   15 +-
 .../internal/configuration/tree/NamedListNode.java |  164 +-
 .../configuration/util/ConfigurationFlattener.java |   32 +-
 .../util/ConfigurationNotificationsUtil.java       |   20 +-
 .../configuration/util/ConfigurationUtil.java      |  700 +++---
 .../util/KeysTrackingConfigurationVisitor.java     |    5 +-
 .../util/WrongPolymorphicTypeIdException.java      |   33 +
 .../configuration/ConfigurationChangerTest.java    |    3 +-
 .../configuration/ConfigurationRegistryTest.java   |  161 +-
 .../configuration/DirectPropertiesTest.java        |    6 +-
 .../configuration/TestConfigurationChanger.java    |   12 +-
 .../asm/ConfigurationAsmGeneratorTest.java         |  358 +++-
 .../configuration/hocon/HoconConverterTest.java    |   78 +-
 .../ConfigurationAnyListenerTest.java              |    1 +
 .../notifications/ConfigurationListenerTest.java   |    1 +
 .../internal/configuration/sample/UsageTest.java   |    2 +
 .../testframework/ConfigurationExtension.java      |    7 +-
 .../testframework/ConfigurationExtensionTest.java  |    2 +-
 .../testframework/InjectConfiguration.java         |   12 +-
 .../configuration/tree/ConfigurationArrayTest.java |    2 +-
 .../tree/ConstructableTreeNodeTest.java            |    2 +-
 .../configuration/tree/NamedListNodeTest.java      |   12 +-
 .../tree/TraversableTreeNodeTest.java              |    2 +-
 .../configuration/util/ConfigurationUtilTest.java  |  434 +++-
 .../validation/ValidationUtilTest.java             |    2 +-
 .../ignite/internal/util/CollectionUtils.java      |  185 +-
 .../ignite/internal/util/CollectionUtilsTest.java  |   42 +-
 .../ignite/utils/ClusterServiceTestUtils.java      |    1 +
 .../ConfigurationPresentationTest.java             |    1 +
 .../ITDistributedConfigurationPropertiesTest.java  |    2 +
 .../ITDistributedConfigurationStorageTest.java     |    1 +
 .../org/apache/ignite/internal/app/IgniteImpl.java |    4 +-
 .../SchemaConfigurationConverterTest.java          |    1 +
 .../ignite/internal/table/TableManagerTest.java    |    2 +-
 84 files changed, 5460 insertions(+), 1203 deletions(-)

diff --git a/modules/bytecode/src/main/java/com/facebook/presto/bytecode/ClassGenerator.java b/modules/bytecode/src/main/java/com/facebook/presto/bytecode/ClassGenerator.java
index 6364b90..612a5fd 100644
--- a/modules/bytecode/src/main/java/com/facebook/presto/bytecode/ClassGenerator.java
+++ b/modules/bytecode/src/main/java/com/facebook/presto/bytecode/ClassGenerator.java
@@ -129,20 +129,7 @@ public class ClassGenerator
         Map<String, byte[]> bytecodes = new LinkedHashMap<>();
 
         for (ClassDefinition classDefinition : classDefinitions) {
-            // We call the simpler class writer first to get any errors out using simpler setting.
-            // This helps when we have large queries that can potentially cause COMPUTE_FRAMES
-            // (used by SmartClassWriter for doing more thorough analysis)
-            ClassWriter simpleClassWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
-            classDefinition.visit(simpleClassWriter);
-            try {
-                simpleClassWriter.toByteArray();
-            }
-            catch (ClassTooLargeException | MethodTooLargeException largeCodeException) {
-                throw new ByteCodeTooLargeException(largeCodeException);
-            }
-            catch (RuntimeException e) {
-                throw new CompilationException("Error compiling class: " + classDefinition.getName(), e);
-            }
+            // Code associated with a simple class writer was removed due to labels reuse when re-generating the bytecode.
 
             ClassWriter writer = new SmartClassWriter(classInfoLoader);
 
diff --git a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ITClientHandlerTest.java b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ITClientHandlerTest.java
index dc44eb0..d5424e3 100644
--- a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ITClientHandlerTest.java
+++ b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ITClientHandlerTest.java
@@ -181,6 +181,7 @@ public class ITClientHandlerTest {
             List.of(ClientConnectorConfiguration.KEY),
             Map.of(),
             new TestConfigurationStorage(LOCAL),
+            List.of(),
             List.of()
         );
 
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 4dce665..db7593c 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
@@ -101,6 +101,7 @@ public abstract class AbstractClientTest {
             List.of(ClientConnectorConfiguration.KEY),
             Map.of(),
             new TestConfigurationStorage(LOCAL),
+            List.of(),
             List.of()
         );
 
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 b871d00..26a0fa1 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
@@ -26,7 +26,6 @@ import org.junit.jupiter.api.Test;
 import static com.google.testing.compile.CompilationSubject.assertThat;
 import static com.google.testing.compile.Compiler.javac;
 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;
 
@@ -47,7 +46,7 @@ public class ITProcessorTest extends AbstractProcessorTest {
 
         final Compilation status = batch.getCompilationStatus();
 
-        assertNotEquals(Compilation.Status.FAILURE, status.status());
+        assertEquals(Compilation.Status.SUCCESS, status.status());
 
         assertEquals(3, batch.generated().size());
 
@@ -68,7 +67,7 @@ public class ITProcessorTest extends AbstractProcessorTest {
 
         BatchCompilation batchCompile = batchCompile(cls0, cls1, cls2, cls3);
 
-        assertNotEquals(Compilation.Status.FAILURE, batchCompile.getCompilationStatus().status());
+        assertEquals(Compilation.Status.SUCCESS, batchCompile.getCompilationStatus().status());
 
         assertEquals(4 * 3, batchCompile.generated().size());
 
@@ -144,6 +143,114 @@ public class ITProcessorTest extends AbstractProcessorTest {
         );
     }
 
+    /** */
+    @Test
+    void testErrorPolymorphicConfigCodeGeneration() {
+        String packageName = "org.apache.ignite.internal.configuration.processor.polymorphic";
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorPolymorphic0ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorPolymorphic1ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorPolymorphic2ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorPolymorphic3ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorPolymorphic4ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorPolymorphic5ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorPolymorphic6ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorPolymorphic7ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorPolymorphic8ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorPolymorphicInstance0ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorPolymorphicInstance1ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorPolymorphicInstance2ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorPolymorphicInstance3ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorPolymorphicInstance4ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorPolymorphicInstance5ConfigurationSchema")
+        );
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> batchCompile(packageName, "ErrorPolymorphicInstance6ConfigurationSchema")
+        );
+    }
+
+    /** */
+    @Test
+    void testSuccessPolymorphicConfigCodeGeneration() {
+        String packageName = "org.apache.ignite.internal.configuration.processor.polymorphic";
+
+        ClassName cls0 = ClassName.get(packageName, "SimplePolymorphicConfigurationSchema");
+        ClassName cls1 = ClassName.get(packageName, "SimplePolymorphicInstanceConfigurationSchema");
+        ClassName cls2 = ClassName.get(packageName, "SimpleConfigurationSchema");
+        ClassName cls3 = ClassName.get(packageName, "SimpleRootConfigurationSchema");
+
+        BatchCompilation batchCompile = batchCompile(cls0, cls1, cls2, cls3);
+
+        assertEquals(Compilation.Status.SUCCESS, 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());
+    }
+
     /**
      * Compile set of classes.
      *
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic0ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic0ConfigurationSchema.java
new file mode 100644
index 0000000..19097c0
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic0ConfigurationSchema.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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+
+/**
+ * Class cannot have {@link PolymorphicConfig} and {@link ConfigurationRoot}.
+ */
+@PolymorphicConfig
+@ConfigurationRoot(rootName = "error")
+public class ErrorPolymorphic0ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic1ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic1ConfigurationSchema.java
new file mode 100644
index 0000000..be32fae
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic1ConfigurationSchema.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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+
+/**
+ * Class cannot have {@link PolymorphicConfig} and {@link Config}.
+ */
+@PolymorphicConfig
+@Config
+public class ErrorPolymorphic1ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic2ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic2ConfigurationSchema.java
new file mode 100644
index 0000000..9dd9da8
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic2ConfigurationSchema.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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+
+/**
+ * Class cannot have {@link PolymorphicConfig} and {@link InternalConfiguration}.
+ */
+@PolymorphicConfig
+@InternalConfiguration
+public class ErrorPolymorphic2ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic3ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic3ConfigurationSchema.java
new file mode 100644
index 0000000..7613587
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic3ConfigurationSchema.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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+
+/**
+ * Class cannot have {@link PolymorphicConfig} and {@link PolymorphicConfigInstance}.
+ */
+@PolymorphicConfig
+@PolymorphicConfigInstance("error")
+public class ErrorPolymorphic3ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic4ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic4ConfigurationSchema.java
new file mode 100644
index 0000000..35851be
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic4ConfigurationSchema.java
@@ -0,0 +1,28 @@
+/*
+ * 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.polymorphic;
+
+import java.util.ArrayList;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+
+/**
+ * Class with {@link PolymorphicConfig} cannot have a super class.
+ */
+@PolymorphicConfig
+public class ErrorPolymorphic4ConfigurationSchema extends ArrayList {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic5ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic5ConfigurationSchema.java
new file mode 100644
index 0000000..59eb2cb
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic5ConfigurationSchema.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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Class with {@link PolymorphicConfig} should contain only one string field with {@link PolymorphicId}.
+ */
+@PolymorphicConfig
+public class ErrorPolymorphic5ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic6ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic6ConfigurationSchema.java
new file mode 100644
index 0000000..78ead06
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic6ConfigurationSchema.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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
+
+/**
+ * Class with {@link PolymorphicConfig} should contain only one string field with {@link PolymorphicId}.
+ */
+@PolymorphicConfig
+public class ErrorPolymorphic6ConfigurationSchema {
+    /** Polymorphic type id field. */
+    @PolymorphicId
+    public int typeId;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic7ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic7ConfigurationSchema.java
new file mode 100644
index 0000000..e031bb1
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic7ConfigurationSchema.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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
+
+/**
+ * Class with {@link PolymorphicConfig} should contain only one string field with {@link PolymorphicId}.
+ */
+@PolymorphicConfig
+public class ErrorPolymorphic7ConfigurationSchema {
+    /** Polymorphic type id field. */
+    @PolymorphicId
+    public String typeId;
+
+    /** Polymorphic type id field 2. */
+    @PolymorphicId
+    public String typeId2;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic8ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic8ConfigurationSchema.java
new file mode 100644
index 0000000..34d8ad4
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic8ConfigurationSchema.java
@@ -0,0 +1,36 @@
+/*
+ * 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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Class with {@link PolymorphicConfig} must contain field {@link PolymorphicId}, which must be the first in the schema.
+ */
+@PolymorphicConfig
+public class ErrorPolymorphic8ConfigurationSchema {
+	/** String value. */
+    @Value
+    public String strVal;
+	
+    /** Polymorphic type id field. */
+    @PolymorphicId
+    public String typeId;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance0ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance0ConfigurationSchema.java
new file mode 100644
index 0000000..59d6d86
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance0ConfigurationSchema.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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+
+/**
+ * Class cannot have {@link PolymorphicConfigInstance} and {@link ConfigurationRoot}.
+ */
+@PolymorphicConfigInstance("error")
+@ConfigurationRoot(rootName = "error")
+public class ErrorPolymorphicInstance0ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance1ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance1ConfigurationSchema.java
new file mode 100644
index 0000000..8dd8c84
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance1ConfigurationSchema.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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+
+/**
+ * Class cannot have {@link PolymorphicConfigInstance} and {@link Config}.
+ */
+@PolymorphicConfigInstance("error")
+@Config
+public class ErrorPolymorphicInstance1ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance2ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance2ConfigurationSchema.java
new file mode 100644
index 0000000..f19ec41
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance2ConfigurationSchema.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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+
+/**
+ * Class cannot have {@link PolymorphicConfigInstance} and {@link InternalConfiguration}.
+ */
+@PolymorphicConfigInstance("error")
+@InternalConfiguration
+public class ErrorPolymorphicInstance2ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance3ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance3ConfigurationSchema.java
new file mode 100644
index 0000000..2d1186e
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance3ConfigurationSchema.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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+
+/**
+ * Class with {@link PolymorphicConfigInstance} must have a super class.
+ */
+@PolymorphicConfigInstance("error")
+public class ErrorPolymorphicInstance3ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance4ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance4ConfigurationSchema.java
new file mode 100644
index 0000000..eea435b
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance4ConfigurationSchema.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.polymorphic;
+
+import java.util.ArrayList;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+
+/**
+ * Class with {@link PolymorphicConfigInstance} must have a super class with {@link PolymorphicConfig}.
+ */
+@PolymorphicConfigInstance("error")
+public class ErrorPolymorphicInstance4ConfigurationSchema extends ArrayList {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance5ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance5ConfigurationSchema.java
new file mode 100644
index 0000000..97808b9
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance5ConfigurationSchema.java
@@ -0,0 +1,33 @@
+/*
+ * 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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Class with {@link PolymorphicConfigInstance} should not contain duplicate fields
+ * (by names) with {@link PolymorphicConfig}.
+ */
+@PolymorphicConfigInstance("error")
+public class ErrorPolymorphicInstance5ConfigurationSchema extends SimplePolymorphicConfigurationSchema {
+    /** String value, duplicate {@link SimplePolymorphicConfigurationSchema#str}. */
+    @Value
+    public String str;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance6ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance6ConfigurationSchema.java
new file mode 100644
index 0000000..3b29ee6
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance6ConfigurationSchema.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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
+
+/**
+ * Class with {@link PolymorphicConfigInstance} should not contain field with {@link PolymorphicId}.
+ */
+@PolymorphicConfigInstance("error")
+public class ErrorPolymorphicInstance6ConfigurationSchema extends SimplePolymorphicConfigurationSchema {
+    /** Polymorphic type id field. */
+    @PolymorphicId
+    public String typeId2;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimpleConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimpleConfigurationSchema.java
new file mode 100644
index 0000000..9268729
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimpleConfigurationSchema.java
@@ -0,0 +1,36 @@
+/*
+ * 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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Configuration schema class with polymorphic configuration.
+ */
+@Config
+public class SimpleConfigurationSchema {
+    /** String value. */
+    @Value
+    public String str;
+
+    /** Polymorphic configuration. */
+    @ConfigValue
+    public SimplePolymorphicConfigurationSchema polymorphicConfig;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimplePolymorphicConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimplePolymorphicConfigurationSchema.java
new file mode 100644
index 0000000..76cfe71
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimplePolymorphicConfigurationSchema.java
@@ -0,0 +1,36 @@
+/*
+ * 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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Simple polymorphic configuration.
+ */
+@PolymorphicConfig
+public class SimplePolymorphicConfigurationSchema {
+    /** Polymorphic type id field. */
+    @PolymorphicId
+    public String typeId;
+
+    /** String value. */
+    @Value
+    public String str;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimplePolymorphicInstanceConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimplePolymorphicInstanceConfigurationSchema.java
new file mode 100644
index 0000000..d174be9
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimplePolymorphicInstanceConfigurationSchema.java
@@ -0,0 +1,36 @@
+/*
+ * 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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Simple instance of a polymorphic configuration.
+ */
+@PolymorphicConfigInstance("test")
+public class SimplePolymorphicInstanceConfigurationSchema extends SimplePolymorphicConfigurationSchema {
+    /** Integer value. */
+    @Value
+    public int i;
+
+    /** String value. */
+    @Value
+    public String str1;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimpleRootConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimpleRootConfigurationSchema.java
new file mode 100644
index 0000000..7c2122a
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimpleRootConfigurationSchema.java
@@ -0,0 +1,36 @@
+/*
+ * 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.polymorphic;
+
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Configuration schema class with polymorphic configuration.
+ */
+@ConfigurationRoot(rootName = "root")
+public class SimpleRootConfigurationSchema {
+    /** String value. */
+    @Value
+    public String str;
+
+    /** Polymorphic configuration. */
+    @ConfigValue
+    public SimplePolymorphicConfigurationSchema polymorphicConfig;
+}
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 c90d8d9..37cc42d 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
@@ -17,13 +17,15 @@
 
 package org.apache.ignite.internal.configuration.processor;
 
-import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.lang.annotation.Annotation;
 import java.util.Collection;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.stream.Stream;
 import javax.annotation.processing.AbstractProcessor;
 import javax.annotation.processing.RoundEnvironment;
 import javax.lang.model.SourceVersion;
@@ -45,16 +47,22 @@ import com.squareup.javapoet.MethodSpec;
 import com.squareup.javapoet.ParameterizedTypeName;
 import com.squareup.javapoet.TypeName;
 import com.squareup.javapoet.TypeSpec;
+import com.squareup.javapoet.TypeVariableName;
 import com.squareup.javapoet.WildcardTypeName;
 import org.apache.ignite.configuration.NamedConfigurationTree;
 import org.apache.ignite.configuration.NamedListChange;
 import org.apache.ignite.configuration.NamedListView;
+import org.apache.ignite.configuration.PolymorphicChange;
+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.DirectAccess;
 import org.apache.ignite.configuration.annotation.InternalConfiguration;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
 import org.apache.ignite.configuration.annotation.Value;
 import org.jetbrains.annotations.Nullable;
 
@@ -64,6 +72,10 @@ import static javax.lang.model.element.Modifier.ABSTRACT;
 import static javax.lang.model.element.Modifier.FINAL;
 import static javax.lang.model.element.Modifier.PUBLIC;
 import static javax.lang.model.element.Modifier.STATIC;
+import static org.apache.ignite.internal.configuration.processor.Utils.joinSimpleName;
+import static org.apache.ignite.internal.configuration.processor.Utils.simpleName;
+import static org.apache.ignite.internal.util.ArrayUtils.nullOrEmpty;
+import static org.apache.ignite.internal.util.CollectionUtils.viewReadOnly;
 
 /**
  * Annotation processor that produces configuration classes.
@@ -72,9 +84,18 @@ public class Processor extends AbstractProcessor {
     /** Java file padding. */
     private static final String INDENT = "    ";
 
-    /** */
+    /** {@link RootKey} class name. */
     private static final ClassName ROOT_KEY_CLASSNAME = ClassName.get("org.apache.ignite.configuration", "RootKey");
 
+    /** {@link PolymorphicChange} class name. */
+    private static final ClassName POLYMORPHIC_CHANGE_CLASSNAME = ClassName.get(PolymorphicChange.class);
+
+    /** Error format for the superclass missing annotation. */
+    private static final String SUPERCLASS_MISSING_ANNOTATION_ERROR_FORMAT = "Superclass must have %s: %s";
+
+    /** Error format for an empty field. */
+    private static final String EMPTY_FIELD_ERROR_FORMAT = "Field %s cannot be empty: %s";
+
     /** {@inheritDoc} */
     @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
         try {
@@ -92,14 +113,14 @@ 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.
+     * @return Whether the set of annotation types are claimed by this processor.
      */
     private boolean process0(RoundEnvironment roundEnvironment) {
         Elements elementUtils = processingEnv.getElementUtils();
 
-        // All classes annotated with @ConfigurationRoot, @Config, @InternalConfiguration.
+        // All classes annotated with {@link #supportedAnnotationTypes}.
         List<TypeElement> annotatedConfigs = roundEnvironment
-            .getElementsAnnotatedWithAny(Set.of(ConfigurationRoot.class, Config.class, InternalConfiguration.class))
+            .getElementsAnnotatedWithAny(supportedAnnotationTypes())
             .stream()
             .filter(element -> element.getKind() == ElementKind.CLASS)
             .map(TypeElement.class::cast)
@@ -110,16 +131,10 @@ public class Processor extends AbstractProcessor {
 
         for (TypeElement clazz : annotatedConfigs) {
             // Find all the fields of the schema.
-            Collection<VariableElement> fields = fields(clazz);
+            List<VariableElement> fields = fields(clazz);
 
             validate(clazz, fields);
 
-            // Is root of the configuration.
-            boolean isRootConfig = clazz.getAnnotation(ConfigurationRoot.class) != null;
-
-            // Is the internal configuration.
-            boolean isInternalConfig = clazz.getAnnotation(InternalConfiguration.class) != null;
-
             // Get package name of the schema class
             String packageName = elementUtils.getPackageOf(clazz).getQualifiedName().toString();
 
@@ -138,44 +153,16 @@ public class Processor extends AbstractProcessor {
                 if (!field.getModifiers().contains(PUBLIC))
                     throw new ProcessorException("Field " + clazz.getQualifiedName() + "." + field + " must be public");
 
-                Element fieldTypeElement = processingEnv.getTypeUtils().asElement(field.asType());
-
                 String fieldName = field.getSimpleName().toString();
 
                 // Get configuration types (VIEW, CHANGE and so on)
                 TypeName interfaceGetMethodType = getInterfaceGetMethodType(field);
 
-                ConfigValue confAnnotation = field.getAnnotation(ConfigValue.class);
-                if (confAnnotation != null) {
-                    if (fieldTypeElement.getAnnotation(Config.class) == null) {
-                        throw new ProcessorException(
-                            "Class for @ConfigValue field must be defined as @Config: " +
-                                clazz.getQualifiedName() + "." + field.getSimpleName()
-                        );
-                    }
-
-                    if (field.getAnnotation(DirectAccess.class) != null) {
-                        throw new ProcessorException(
-                            "@DirectAccess annotation must not be present on nested configuration fields"
-                        );
-                    }
-                }
+                if (field.getAnnotation(ConfigValue.class) != null)
+                    checkConfigField(field, ConfigValue.class);
 
-                NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
-                if (namedConfigAnnotation != null) {
-                    if (fieldTypeElement.getAnnotation(Config.class) == null) {
-                        throw new ProcessorException(
-                            "Class for @NamedConfigValue field must be defined as @Config: " +
-                                clazz.getQualifiedName() + "." + field.getSimpleName()
-                        );
-                    }
-
-                    if (field.getAnnotation(DirectAccess.class) != null) {
-                        throw new ProcessorException(
-                            "@DirectAccess annotation must not be present on nested configuration fields"
-                        );
-                    }
-                }
+                if (field.getAnnotation(NamedConfigValue.class) != null)
+                    checkConfigField(field, NamedConfigValue.class);
 
                 Value valueAnnotation = field.getAnnotation(Value.class);
                 if (valueAnnotation != null) {
@@ -189,16 +176,42 @@ public class Processor extends AbstractProcessor {
                     }
                 }
 
+                PolymorphicId polymorphicId = field.getAnnotation(PolymorphicId.class);
+                if (polymorphicId != null) {
+                    if (!isStringClass(field.asType())) {
+                        throw new ProcessorException(String.format(
+                            "%s %s.%s field field must be String.",
+                            simpleName(PolymorphicId.class),
+                            clazz.getQualifiedName(),
+                            field.getSimpleName()
+                        ));
+                    }
+                }
+
                 createGetters(configurationInterfaceBuilder, fieldName, interfaceGetMethodType);
             }
 
+            // Is root of the configuration.
+            boolean isRootConfig = clazz.getAnnotation(ConfigurationRoot.class) != null;
+
+            // Is the internal configuration.
+            boolean isInternalConfig = clazz.getAnnotation(InternalConfiguration.class) != null;
+
+            // Is a polymorphic configuration.
+            boolean isPolymorphicConfig = clazz.getAnnotation(PolymorphicConfig.class) != null;
+
+            // Is an instance of a polymorphic configuration.
+            boolean isPolymorphicInstance = clazz.getAnnotation(PolymorphicConfigInstance.class) != null;
+
             // Create VIEW and CHANGE classes.
             createPojoBindings(
                 fields,
                 schemaClassName,
                 configurationInterfaceBuilder,
-                isInternalConfig && !isRootConfig,
-                clazz
+                (isInternalConfig && !isRootConfig) || isPolymorphicInstance,
+                clazz,
+                isPolymorphicConfig,
+                isPolymorphicInstance
             );
 
             if (isRootConfig)
@@ -236,9 +249,9 @@ public class Processor extends AbstractProcessor {
     /**
      * Create getters for configuration class.
      *
-     * @param configurationInterfaceBuilder
-     * @param fieldName
-     * @param interfaceGetMethodType
+     * @param configurationInterfaceBuilder Interface builder.
+     * @param fieldName Field name.
+     * @param interfaceGetMethodType Return type.
      */
     private static void createGetters(
         TypeSpec.Builder configurationInterfaceBuilder,
@@ -255,7 +268,8 @@ public class Processor extends AbstractProcessor {
 
     /**
      * Get types for configuration classes generation.
-     * @param field
+     *
+     * @param field Field.
      * @return Bundle with all types for configuration
      */
     private static TypeName getInterfaceGetMethodType(VariableElement field) {
@@ -274,11 +288,17 @@ public class Processor extends AbstractProcessor {
             TypeName viewClassType = Utils.getViewName((ClassName) baseType);
             TypeName changeClassType = Utils.getChangeName((ClassName) baseType);
 
-            interfaceGetMethodType = ParameterizedTypeName.get(ClassName.get(NamedConfigurationTree.class), interfaceGetType, viewClassType, changeClassType);
+            interfaceGetMethodType = ParameterizedTypeName.get(
+                ClassName.get(NamedConfigurationTree.class),
+                interfaceGetType,
+                viewClassType,
+                changeClassType
+            );
         }
 
         Value valueAnnotation = field.getAnnotation(Value.class);
-        if (valueAnnotation != null) {
+        PolymorphicId polymorphicIdAnnotation = field.getAnnotation(PolymorphicId.class);
+        if (valueAnnotation != null || polymorphicIdAnnotation != null) {
             // It is necessary to use class names without loading classes so that we won't
             // accidentally get NoClassDefFoundError
             ClassName confValueClass = ClassName.get("org.apache.ignite.configuration", "ConfigurationValue");
@@ -297,18 +317,22 @@ public class Processor extends AbstractProcessor {
     /**
      * Create VIEW and CHANGE classes and methods.
      *
-     * @param fields List of configuration fields.
+     * @param fields Collection 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.
+     * @param isPolymorphicConfig Is a polymorphic configuration.
+     * @param isPolymorphicInstanceConfig Is an instance of polymorphic configuration.
      */
     private void createPojoBindings(
         Collection<VariableElement> fields,
         ClassName schemaClassName,
         TypeSpec.Builder configurationInterfaceBuilder,
         boolean extendBaseSchema,
-        TypeElement realSchemaClass
+        TypeElement realSchemaClass,
+        boolean isPolymorphicConfig,
+        boolean isPolymorphicInstanceConfig
     ) {
         ClassName viewClsName = Utils.getViewName(schemaClassName);
         ClassName changeClsName = Utils.getChangeName(schemaClassName);
@@ -352,6 +376,9 @@ public class Processor extends AbstractProcessor {
         if (changeBaseSchemaInterfaceType != null)
             changeClsBuilder.addSuperinterface(changeBaseSchemaInterfaceType);
 
+        if (isPolymorphicInstanceConfig)
+            changeClsBuilder.addSuperinterface(POLYMORPHIC_CHANGE_CLASSNAME);
+
         ClassName consumerClsName = ClassName.get(Consumer.class);
 
         for (VariableElement field : fields) {
@@ -385,36 +412,54 @@ public class Processor extends AbstractProcessor {
                 );
             }
 
-            {
-                MethodSpec.Builder getMtdBuilder = MethodSpec.methodBuilder(fieldName)
-                    .addModifiers(PUBLIC, ABSTRACT)
-                    .returns(viewFieldType);
+            MethodSpec.Builder getMtdBuilder = MethodSpec.methodBuilder(fieldName)
+                .addModifiers(PUBLIC, ABSTRACT)
+                .returns(viewFieldType);
 
-                viewClsBuilder.addMethod(getMtdBuilder.build());
-            }
+            viewClsBuilder.addMethod(getMtdBuilder.build());
 
-            {
-                String changeMtdName = "change" + capitalize(fieldName);
+            // Read only.
+            if (field.getAnnotation(PolymorphicId.class) != null)
+                continue;
 
-                {
-                    MethodSpec.Builder changeMtdBuilder = MethodSpec.methodBuilder(changeMtdName)
-                        .addModifiers(PUBLIC, ABSTRACT)
-                        .returns(changeClsName);
+            String changeMtdName = "change" + capitalize(fieldName);
 
-                    if (valAnnotation != null) {
-                        if (schemaFieldType.getKind() == TypeKind.ARRAY)
-                            changeMtdBuilder.varargs(true);
+            MethodSpec.Builder changeMtdBuilder = MethodSpec.methodBuilder(changeMtdName)
+                .addModifiers(PUBLIC, ABSTRACT)
+                .returns(changeClsName);
 
-                        changeMtdBuilder.addParameter(changeFieldType, fieldName);
-                    }
-                    else
-                        changeMtdBuilder.addParameter(ParameterizedTypeName.get(consumerClsName, changeFieldType), fieldName);
+            if (valAnnotation != null) {
+                if (schemaFieldType.getKind() == TypeKind.ARRAY)
+                    changeMtdBuilder.varargs(true);
 
-                    changeClsBuilder.addMethod(changeMtdBuilder.build());
-                }
+                changeMtdBuilder.addParameter(changeFieldType, fieldName);
             }
+            else
+                changeMtdBuilder.addParameter(ParameterizedTypeName.get(consumerClsName, changeFieldType), fieldName);
+
+            changeClsBuilder.addMethod(changeMtdBuilder.build());
         }
 
+        if (isPolymorphicConfig) {
+            // Parameter type: Class<T>.
+            ParameterizedTypeName parameterType = ParameterizedTypeName.get(
+                ClassName.get(Class.class),
+                TypeVariableName.get("T")
+            );
+
+            // Variable type, for example: <T extends SimpleChange & PolymorphicInstance>.
+            TypeVariableName typeVariable = TypeVariableName.get("T", changeClsName, POLYMORPHIC_CHANGE_CLASSNAME);
+
+            // Method like: <T extends SimpleChange> T convert(Class<T> changeClass);
+            MethodSpec.Builder convertMtdBuilder = MethodSpec.methodBuilder("convert")
+                .addModifiers(PUBLIC, ABSTRACT)
+                .addTypeVariable(typeVariable)
+                .addParameter(parameterType, "changeClass")
+                .returns(TypeVariableName.get("T"));
+
+            changeClsBuilder.addMethod(convertMtdBuilder.build());
+        }
+        
         TypeSpec viewCls = viewClsBuilder.build();
         TypeSpec changeCls = changeClsBuilder.build();
 
@@ -430,8 +475,8 @@ public class Processor extends AbstractProcessor {
                 .build()
                 .writeTo(processingEnv.getFiler());
         }
-        catch (IOException e) {
-            throw new ProcessorException("Failed to generate class " + packageName + "." + cls.name, e);
+        catch (Throwable throwable) {
+            throw new ProcessorException("Failed to generate class " + packageName + "." + cls.name, throwable);
         }
     }
 
@@ -482,7 +527,7 @@ public class Processor extends AbstractProcessor {
      * @param type Class type.
      * @return Class fields.
      */
-    private static Collection<VariableElement> fields(TypeElement type) {
+    private static List<VariableElement> fields(TypeElement type) {
         return type.getEnclosedElements().stream()
             .filter(el -> el.getKind() == ElementKind.FIELD)
             .map(VariableElement.class::cast)
@@ -496,88 +541,364 @@ public class Processor extends AbstractProcessor {
      * @param fields Class fields.
      * @throws ProcessorException If the class validation fails.
      */
-    private void validate(TypeElement clazz, Collection<VariableElement> fields) {
+    private void validate(TypeElement clazz, List<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())) {
+            checkIncompatibleClassAnnotations(
+                clazz,
+                InternalConfiguration.class,
+                Config.class, PolymorphicConfig.class, PolymorphicConfigInstance.class
+            );
+
+            checkNotContainsPolymorphicIdField(clazz, InternalConfiguration.class, fields);
+
+            if (clazz.getAnnotation(ConfigurationRoot.class) != null)
+                checkNotExistSuperClass(clazz, InternalConfiguration.class);
+            else {
+                checkExistSuperClass(clazz, InternalConfiguration.class);
+
+                TypeElement superClazz = superClass(clazz);
+
+                if (superClazz.getAnnotation(InternalConfiguration.class) != null) {
                     throw new ProcessorException(String.format(
-                        "Class with @%s and @%s should not have a superclass: %s",
-                        ConfigurationRoot.class.getSimpleName(),
-                        InternalConfiguration.class.getSimpleName(),
+                        "Superclass must not have %s: %s",
+                        simpleName(InternalConfiguration.class),
                         clazz.getQualifiedName()
                     ));
                 }
+
+                checkSuperclassContainAnyAnnotation(clazz, superClazz, ConfigurationRoot.class, Config.class);
+
+                checkNoConflictFieldNames(clazz, superClazz, fields, fields(superClazz));
             }
-            else if (isObjectClass(clazz.getSuperclass())) {
+        }
+        else if (clazz.getAnnotation(PolymorphicConfig.class) != null) {
+            checkIncompatibleClassAnnotations(
+                clazz,
+                PolymorphicConfig.class,
+                ConfigurationRoot.class, Config.class, PolymorphicConfigInstance.class
+            );
+
+            checkNotExistSuperClass(clazz, PolymorphicConfig.class);
+
+            List<VariableElement> typeIdFields = collectAnnotatedFields(fields, PolymorphicId.class);
+
+            if (typeIdFields.size() != 1 || fields.indexOf(typeIdFields.get(0)) != 0) {
                 throw new ProcessorException(String.format(
-                    "Class with @%s must have a superclass: %s",
-                    InternalConfiguration.class.getSimpleName(),
+                    "Class with %s must contain one field with %s and it should be the first in the schema: %s",
+                    simpleName(PolymorphicConfig.class),
+                    simpleName(PolymorphicId.class),
                     clazz.getQualifiedName()
                 ));
             }
-            else {
-                TypeElement superClazz = processingEnv
-                    .getElementUtils()
-                    .getTypeElement(clazz.getSuperclass().toString());
+        }
+        else if (clazz.getAnnotation(PolymorphicConfigInstance.class) != null) {
+            checkIncompatibleClassAnnotations(
+                clazz,
+                PolymorphicConfigInstance.class,
+                ConfigurationRoot.class, Config.class
+            );
 
-                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());
+            checkNotContainsPolymorphicIdField(clazz, PolymorphicConfigInstance.class, fields);
 
-                    Collection<Name> duplicateFieldNames = fields.stream()
-                        .map(VariableElement::getSimpleName)
-                        .filter(superClazzFieldNames::contains)
-                        .collect(toList());
+            String id = clazz.getAnnotation(PolymorphicConfigInstance.class).value();
 
-                    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
-                        ));
-                    }
-                }
+            if (id == null || id.isBlank()) {
+                throw new ProcessorException(String.format(
+                    EMPTY_FIELD_ERROR_FORMAT,
+                    simpleName(PolymorphicConfigInstance.class) + ".id()",
+                    clazz.getQualifiedName()
+                ));
             }
+
+            checkExistSuperClass(clazz, PolymorphicConfigInstance.class);
+
+            TypeElement superClazz = superClass(clazz);
+
+            checkSuperclassContainAnyAnnotation(clazz, superClazz, PolymorphicConfig.class);
+
+            checkNoConflictFieldNames(clazz, superClazz, fields, fields(superClazz));
         }
+        else if (clazz.getAnnotation(ConfigurationRoot.class) != null)
+            checkNotContainsPolymorphicIdField(clazz, ConfigurationRoot.class, fields);
+        else if (clazz.getAnnotation(Config.class) != null)
+            checkNotContainsPolymorphicIdField(clazz, Config.class, fields);
     }
 
     /** {@inheritDoc} */
     @Override public Set<String> getSupportedAnnotationTypes() {
-        return Set.of(
-            Config.class.getCanonicalName(),
-            ConfigurationRoot.class.getCanonicalName(),
-            InternalConfiguration.class.getCanonicalName()
-        );
+        return Set.copyOf(viewReadOnly(supportedAnnotationTypes(), Class::getCanonicalName));
     }
 
     /** {@inheritDoc} */
     @Override public SourceVersion getSupportedSourceVersion() {
         return SourceVersion.latest();
     }
+
+    /**
+     * @return Annotation types supported by this processor.
+     */
+    private Set<Class<? extends Annotation>> supportedAnnotationTypes() {
+        return Set.of(
+            Config.class,
+            ConfigurationRoot.class,
+            InternalConfiguration.class,
+            PolymorphicConfig.class,
+            PolymorphicConfigInstance.class
+        );
+    }
+
+    /**
+     * Getting a superclass.
+     *
+     * @param clazz Class type.
+     * @return Superclass type.
+     */
+    private TypeElement superClass(TypeElement clazz) {
+        return processingEnv.getElementUtils().getTypeElement(clazz.getSuperclass().toString());
+    }
+
+    /**
+     * Returns the first annotation found for the class.
+     *
+     * @param clazz Class type.
+     * @param annotationClasses Annotation classes that will be searched for the class.
+     * @return First annotation found.
+     */
+    @SafeVarargs
+    @Nullable private static Annotation findFirst(
+        TypeElement clazz,
+        Class<? extends Annotation>... annotationClasses
+    ) {
+        return Stream.of(annotationClasses).map(clazz::getAnnotation).filter(Objects::nonNull).findFirst().orElse(null);
+    }
+
+    /**
+     * Search for duplicate class fields by name.
+     *
+     * @param fields1 First class fields.
+     * @param fields2 Second class fields.
+     * @return Field names.
+     */
+    private static Collection<Name> findDuplicates(
+        Collection<VariableElement> fields1,
+        Collection<VariableElement> fields2
+    ) {
+        if (fields1.isEmpty() || fields2.isEmpty())
+            return List.of();
+
+        Set<Name> filedNames1 = fields1.stream()
+            .map(VariableElement::getSimpleName)
+            .collect(toSet());
+
+        return fields2.stream()
+            .map(VariableElement::getSimpleName)
+            .filter(filedNames1::contains)
+            .collect(toList());
+    }
+
+    /**
+     * Checking a class field with annotations {@link ConfigValue} or {@link NamedConfigValue}.
+     *
+     * @param field Class field.
+     * @param annotationClass Field annotation: {@link ConfigValue} or {@link NamedConfigValue}.
+     * @throws ProcessorException If the check is not successful.
+     */
+    private void checkConfigField(
+        VariableElement field,
+        Class<? extends Annotation> annotationClass
+    ) {
+        assert annotationClass == ConfigValue.class || annotationClass == NamedConfigValue.class : annotationClass;
+        assert field.getAnnotation(annotationClass) != null : field.getEnclosingElement() + "." + field;
+
+        Element fieldTypeElement = processingEnv.getTypeUtils().asElement(field.asType());
+
+        if (fieldTypeElement.getAnnotation(Config.class) == null &&
+            fieldTypeElement.getAnnotation(PolymorphicConfig.class) == null) {
+            throw new ProcessorException(String.format(
+                "Class for %s field must be defined as %s: %s.%s",
+                simpleName(annotationClass),
+                joinSimpleName(" or ", Config.class, PolymorphicConfig.class),
+                field.getEnclosingElement(),
+                field.getSimpleName()
+            ));
+        }
+
+        if (field.getAnnotation(DirectAccess.class) != null) {
+            throw new ProcessorException(String.format(
+                "%s annotation must not be present on nested configuration fields: %s.%s",
+                simpleName(DirectAccess.class),
+                field.getEnclosingElement(),
+                field.getSimpleName()
+            ));
+        }
+    }
+
+    /**
+     * Check if a class type is {@link String}.
+     *
+     * @param type Class type.
+     * @return {@code true} if class type is {@link String}.
+     */
+    private boolean isStringClass(TypeMirror type) {
+        TypeMirror objectType = processingEnv
+            .getElementUtils()
+            .getTypeElement(String.class.getCanonicalName())
+            .asType();
+
+        return objectType.equals(type);
+    }
+
+    /**
+     * Collect fields with annotation.
+     *
+     * @param fields Fields.
+     * @param annotationClass Annotation class.
+     * @return Fields with annotation.
+     */
+    private static List<VariableElement> collectAnnotatedFields(
+        Collection<VariableElement> fields,
+        Class<? extends Annotation> annotationClass
+    ) {
+        return fields.stream().filter(f -> f.getAnnotation(annotationClass) != null).collect(toList());
+    }
+
+    /**
+     * Checks for an incompatible class annotation with {@code clazzAnnotation}.
+     *
+     * @param clazz Class type.
+     * @param clazzAnnotation Class annotation.
+     * @param incompatibleAnnotations Incompatible class annotations with {@code clazzAnnotation}.
+     * @throws ProcessorException If there is an incompatible class annotation with {@code clazzAnnotation}.
+     */
+    private void checkIncompatibleClassAnnotations(
+        TypeElement clazz,
+        Class<? extends Annotation> clazzAnnotation,
+        Class<? extends Annotation>... incompatibleAnnotations
+    ) {
+        assert clazz.getAnnotation(clazzAnnotation) != null : clazz.getQualifiedName();
+        assert !nullOrEmpty(incompatibleAnnotations);
+
+        Annotation incompatible = findFirst(clazz, incompatibleAnnotations);
+
+        if (incompatible != null) {
+            throw new ProcessorException(String.format(
+                "Class with %s is not allowed with %s: %s",
+                simpleName(incompatible.getClass()),
+                simpleName(clazzAnnotation),
+                clazz.getQualifiedName()
+            ));
+        }
+    }
+
+    /**
+     * Checks that the class has a superclass.
+     *
+     * @param clazz Class type.
+     * @param clazzAnnotation Class annotation.
+     * @throws ProcessorException If the class doesn't have a superclass.
+     */
+    private void checkExistSuperClass(TypeElement clazz, Class<? extends Annotation> clazzAnnotation) {
+        assert clazz.getAnnotation(clazzAnnotation) != null : clazz.getQualifiedName();
+
+        if (isObjectClass(clazz.getSuperclass())) {
+            throw new ProcessorException(String.format(
+                "Class with %s should not have a superclass: %s",
+                simpleName(clazzAnnotation),
+                clazz.getQualifiedName()
+            ));
+        }
+    }
+
+    /**
+     * Checks that the class should not have a superclass.
+     *
+     * @param clazz Class type.
+     * @param clazzAnnotation Class annotation.
+     * @throws ProcessorException If the class have a superclass.
+     */
+    private void checkNotExistSuperClass(TypeElement clazz, Class<? extends Annotation> clazzAnnotation) {
+        assert clazz.getAnnotation(clazzAnnotation) != null : clazz.getQualifiedName();
+
+        if (!isObjectClass(clazz.getSuperclass())) {
+            throw new ProcessorException(String.format(
+                "Class with %s should not have a superclass: %s",
+                simpleName(clazzAnnotation),
+                clazz.getQualifiedName()
+            ));
+        }
+    }
+
+    /**
+     * Checks that the class does not have a field with {@link PolymorphicId}.
+     *
+     * @param clazz Class type.
+     * @param clazzAnnotation Class annotation.
+     * @param clazzfields Class fields.
+     * @throws ProcessorException If the class has a field with {@link PolymorphicId}.
+     */
+    private void checkNotContainsPolymorphicIdField(
+        TypeElement clazz,
+        Class<? extends Annotation> clazzAnnotation,
+        List<VariableElement> clazzfields
+    ) {
+        assert clazz.getAnnotation(clazzAnnotation) != null : clazz.getQualifiedName();
+
+        if (!collectAnnotatedFields(clazzfields, PolymorphicId.class).isEmpty()) {
+            throw new ProcessorException(String.format(
+                "Class with %s cannot have a field with %s: %s",
+                simpleName(clazzAnnotation),
+                simpleName(PolymorphicId.class),
+                clazz.getQualifiedName()
+            ));
+        }
+    }
+
+    /**
+     * Checks that there is no conflict of field names between classes.
+     *
+     * @param clazz0 First class type.
+     * @param clazz1 Second class type.
+     * @param clazzFields0 First class fields.
+     * @param clazzFields1 Second class fields.
+     * @throws ProcessorException If there is a conflict of field names between classes.
+     */
+    private void checkNoConflictFieldNames(
+        TypeElement clazz0,
+        TypeElement clazz1,
+        List<VariableElement> clazzFields0,
+        List<VariableElement> clazzFields1
+    ) {
+        Collection<Name> duplicateFieldNames = findDuplicates(clazzFields0, clazzFields1);
+
+        if (!duplicateFieldNames.isEmpty()) {
+            throw new ProcessorException(String.format(
+                "Duplicate field names are not allowed [class=%s, superClass=%s, fields=%s]",
+                clazz0.getQualifiedName(),
+                clazz1.getQualifiedName(),
+                duplicateFieldNames
+            ));
+        }
+    }
+
+    /**
+     * Checks if the superclass has at least one annotation from {@code superClazzAnnotations}.
+     *
+     * @param clazz Class type.
+     * @param superClazz Superclass type.
+     * @param superClazzAnnotations Superclass annotations.
+     * @throws ProcessorException If the superclass has none of the annotations from {@code superClazzAnnotations}.
+     */
+    private void checkSuperclassContainAnyAnnotation(
+        TypeElement clazz,
+        TypeElement superClazz,
+        Class<? extends Annotation>... superClazzAnnotations
+    ) {
+        if (Stream.of(superClazzAnnotations).allMatch(a -> superClazz.getAnnotation(a) == null)) {
+            throw new ProcessorException(String.format(
+                SUPERCLASS_MISSING_ANNOTATION_ERROR_FORMAT,
+                joinSimpleName(" or ", superClazzAnnotations),
+                clazz.getQualifiedName()
+            ));
+        }
+    }
 }
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/Utils.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/Utils.java
index ffa0b4f..e7c6790 100644
--- a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/Utils.java
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/Utils.java
@@ -16,8 +16,12 @@
  */
 package org.apache.ignite.internal.configuration.processor;
 
+import java.lang.annotation.Annotation;
+import java.util.stream.Stream;
 import com.squareup.javapoet.ClassName;
 
+import static java.util.stream.Collectors.joining;
+
 /**
  * Annotation processing utilities.
  */
@@ -64,4 +68,26 @@ public class Utils {
             schemaClassName.simpleName().replace("ConfigurationSchema", "Change")
         );
     }
+
+    /**
+     * Returns the simple name of the annotation as: @Config.
+     *
+     * @param annotationClass Annotation class.
+     * @return Simple name of the annotation.
+     */
+    public static String simpleName(Class<? extends Annotation> annotationClass) {
+        return '@' + annotationClass.getSimpleName();
+    }
+
+    /**
+     * Create a string with simple annotation names like: @Config and @PolymorphicConfig.
+     *
+     * @param delimiter Delimiter between elements.
+     * @param annotations Annotations.
+     * @return String with simple annotation names.
+     */
+    @SafeVarargs
+    public static String joinSimpleName(String delimiter, Class<? extends Annotation>... annotations) {
+        return Stream.of(annotations).map(Utils::simpleName).collect(joining(delimiter));
+    }
 }
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/processor/UtilsTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/processor/UtilsTest.java
new file mode 100644
index 0000000..a829f89
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/processor/UtilsTest.java
@@ -0,0 +1,44 @@
+package org.apache.ignite.internal.configuration.processor;/*
+ * 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.
+ */
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.internal.configuration.processor.Utils;
+import org.junit.jupiter.api.Test;
+
+import static org.apache.ignite.internal.configuration.processor.Utils.joinSimpleName;
+import static org.apache.ignite.internal.configuration.processor.Utils.simpleName;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Class for testing the {@link Utils}.
+ */
+public class UtilsTest {
+    /** */
+    @Test
+    void testSimpleName() {
+        assertEquals("@Config", simpleName(Config.class));
+    }
+
+    /** */
+    @Test
+    void testJoinSimpleName() {
+        assertEquals("@Config", joinSimpleName(" and ", Config.class));
+        assertEquals("@Config or @ConfigurationRoot", joinSimpleName(" or ", Config.class, ConfigurationRoot.class));
+        assertEquals("", joinSimpleName(" or "));
+    }
+}
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationReadOnlyException.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationReadOnlyException.java
new file mode 100644
index 0000000..b485d8f
--- /dev/null
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationReadOnlyException.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * This exception is used if an attempt was made to update a configuration value in read-only mode.
+ */
+public class ConfigurationReadOnlyException extends RuntimeException {
+    /**
+     * Constructor.
+     *
+     * @param message Error message.
+     */
+    public ConfigurationReadOnlyException(String message) {
+        super(message);
+    }
+}
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationWrongPolymorphicTypeIdException.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationWrongPolymorphicTypeIdException.java
new file mode 100644
index 0000000..88da743
--- /dev/null
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationWrongPolymorphicTypeIdException.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * Thrown when the wrong (unknown) type of polymorphic configuration is used.
+ */
+public class ConfigurationWrongPolymorphicTypeIdException extends RuntimeException {
+    /**
+     * Constructor.
+     *
+     * @param message Error message.
+     */
+    public ConfigurationWrongPolymorphicTypeIdException(String message) {
+        super(message);
+    }
+}
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java
index bded5d9..b4029a2 100644
--- a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java
@@ -24,10 +24,10 @@ import org.apache.ignite.configuration.notifications.ConfigurationNotificationEv
 /**
  * Closure parameter for {@link NamedConfigurationTree#change(Consumer)} method. Contains methods to modify named lists.
  *
- * @param <View> Type for the reading named list elements of this particular list.
- * @param <Change> Type for changing named list elements of this particular list.
+ * @param <VIEW> Type for the reading named list elements of this particular list.
+ * @param <CHANGE> Type for changing named list elements of this particular list.
  */
-public interface NamedListChange<View, Change extends View> extends NamedListView<View> {
+public interface NamedListChange<VIEW, CHANGE extends VIEW> extends NamedListView<VIEW> {
     /**
      * Creates a new value in the named list configuration.
      *
@@ -39,7 +39,7 @@ public interface NamedListChange<View, Change extends View> extends NamedListVie
      * @throws NullPointerException If one of the parameters is null.
      * @throws IllegalArgumentException If an element with the given name already exists.
      */
-    NamedListChange<View, Change> create(String key, Consumer<Change> valConsumer);
+    NamedListChange<VIEW, CHANGE> create(String key, Consumer<CHANGE> valConsumer);
 
     /**
      * Creates a new value at the given position in the named list configuration.
@@ -54,7 +54,7 @@ public interface NamedListChange<View, Change extends View> extends NamedListVie
      * @throws IndexOutOfBoundsException If index is negative of exceeds the size of the list.
      * @throws IllegalArgumentException If an element with the given name already exists.
      */
-    NamedListChange<View, Change> create(int index, String key, Consumer<Change> valConsumer);
+    NamedListChange<VIEW, CHANGE> create(int index, String key, Consumer<CHANGE> valConsumer);
 
     /**
      * Create a new value after a given precedingKey key in the named list configuration.
@@ -69,7 +69,7 @@ public interface NamedListChange<View, Change extends View> extends NamedListVie
      * @throws IllegalArgumentException If element with given name already exists
      *      or if {@code precedingKey} element doesn't exist.
      */
-    NamedListChange<View, Change> createAfter(String precedingKey, String key, Consumer<Change> valConsumer);
+    NamedListChange<VIEW, CHANGE> createAfter(String precedingKey, String key, Consumer<CHANGE> valConsumer);
 
     /**
      * Updates a value in the named list configuration. If the value cannot be found, creates a new one instead.
@@ -82,7 +82,7 @@ public interface NamedListChange<View, Change extends View> extends NamedListVie
      * @throws NullPointerException If one of parameters is null.
      * @throws IllegalArgumentException If {@link #delete(String)} has been invoked with the same key previously.
      */
-    NamedListChange<View, Change> createOrUpdate(String key, Consumer<Change> valConsumer);
+    NamedListChange<VIEW, CHANGE> createOrUpdate(String key, Consumer<CHANGE> valConsumer);
 
     /**
      * Renames the existing value in the named list configuration. Element with key {@code oldKey} must exist and key
@@ -100,7 +100,7 @@ public interface NamedListChange<View, Change extends View> extends NamedListVie
      *      {@code oldKey} doesn't exist, or {@link #delete(String)} has previously been invoked with either the
      *      {@code newKey} or the {@code oldKey}.
      */
-    NamedListChange<View, Change> rename(String oldKey, String newKey);
+    NamedListChange<VIEW, CHANGE> rename(String oldKey, String newKey);
 
     /**
      * Removes the value from the named list configuration.
@@ -112,5 +112,5 @@ public interface NamedListChange<View, Change extends View> extends NamedListVie
      * @throws IllegalArgumentException If {@link #createOrUpdate(String, Consumer)} has been invoked with the same key
      *      previously.
      */
-    NamedListChange<View, Change> delete(String key);
+    NamedListChange<VIEW, CHANGE> delete(String key);
 }
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListView.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListView.java
index c422cb3..ce49522 100644
--- a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListView.java
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListView.java
@@ -22,9 +22,9 @@ import java.util.List;
 /**
  * View type for a {@link NamedConfigurationTree}. Represents an immutable snapshot of a named list configuration.
  *
- * @param <View> Type for immutable snapshots of named list elements.
+ * @param <VIEW> Type for immutable snapshots of named list elements.
  */
-public interface NamedListView<View> {
+public interface NamedListView<VIEW> {
     /**
      * Returns an immutable collection of keys contained within this list.
      *
@@ -38,7 +38,7 @@ public interface NamedListView<View> {
      * @param key Key string.
      * @return Requested value or {@code null} if it's not found.
      */
-    View get(String key);
+    VIEW get(String key);
 
     /**
      * Returns value located at the specified index.
@@ -47,7 +47,7 @@ public interface NamedListView<View> {
      * @return Requested value.
      * @throws IndexOutOfBoundsException If index is out of bounds.
      */
-    View get(int index) throws IndexOutOfBoundsException;
+    VIEW get(int index) throws IndexOutOfBoundsException;
 
     /**
      * Returns the number of elements in this list.
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/PolymorphicChange.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/PolymorphicChange.java
new file mode 100644
index 0000000..1a19392
--- /dev/null
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/PolymorphicChange.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+
+/**
+ * Marker interface for {@code *Node} classes whose schemas are marked with annotation {@link PolymorphicConfigInstance},
+ * this will allow at the compilation stage to distinguish an instance of a polymorphic configuration from
+ * the polymorphic configuration itself.
+ */
+public interface PolymorphicChange {
+}
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicConfig.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicConfig.java
new file mode 100644
index 0000000..2066436
--- /dev/null
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicConfig.java
@@ -0,0 +1,40 @@
+/*
+ * 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 marks the class as a polymorphic configuration schema.
+ * Has basically the same properties as a {@link Config}, but it should be treated like an abstract class in java.
+ *
+ * <p>To change the type of polymorphic configuration, you must use the {@code PolymorphicConfigChange#convert}.
+ *
+ * <p>NOTE: {@link PolymorphicId} field must go first, you should explicitly declare that, also at least one
+ * {@link PolymorphicConfigInstance} is required.
+ */
+@Target(TYPE)
+@Retention(RUNTIME)
+@Documented
+public @interface PolymorphicConfig {
+}
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicConfigInstance.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicConfigInstance.java
new file mode 100644
index 0000000..cfc2574
--- /dev/null
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicConfigInstance.java
@@ -0,0 +1,43 @@
+/*
+ * 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 marks the class as an instance of polymorphic configuration schema.
+ * Has basically the same properties as {@link Config}, but must inherit from the {@link PolymorphicConfig}.
+ *
+ * <p>NOTE: Field name conflicts with the parent ({@link PolymorphicConfig}) are not allowed.
+ */
+@Target(TYPE)
+@Retention(RUNTIME)
+@Documented
+public @interface PolymorphicConfigInstance {
+    /**
+     * Unique identifier for an extension within a single {@link PolymorphicConfig polymorphic configuration}.
+     *
+     * @return Unique identifier for the extension of the polymorphic configuration instance.
+     */
+    String value();
+}
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicId.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicId.java
new file mode 100644
index 0000000..0af7aa4
--- /dev/null
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicId.java
@@ -0,0 +1,43 @@
+/*
+ * 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.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * This annotation marks the {@link PolymorphicConfig polymorphic configuration schema} field as a special
+ * (read only) leaf that will store the current {@link PolymorphicConfigInstance#value polymorphic configuration type}.
+ *
+ * <p>NOTE: Field must be the first in the schema, and the type must be {@link String}.
+ */
+@Target(FIELD)
+@Retention(RUNTIME)
+@Documented
+public @interface PolymorphicId {
+    /**
+     * Indicates that the field contains a default value that should be equal to one of the {@link PolymorphicConfigInstance#value}.
+     *
+     * @return {@code hasDefault} flag value.
+     */
+    boolean hasDefault() default false;
+}
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 382a8d5..dc600c1 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
@@ -59,6 +59,7 @@ import static java.util.stream.Collectors.toMap;
 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.compressDeletedEntries;
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.dropNulls;
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.fillFromPrefixMap;
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.toPrefixMap;
@@ -451,24 +452,4 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
                     oldStorageRoots.changeFuture.completeExceptionally(t);
             });
     }
-
-    /**
-     * "Compress" prefix map - this means that deleted named list elements will be represented as a single {@code null}
-     * objects instead of a number of nullified configuration leaves.
-     *
-     * @param prefixMap Prefix map, constructed from the storage notification data or its subtree.
-     */
-    private void compressDeletedEntries(Map<String, ?> prefixMap) {
-        // Here we basically assume that if prefix subtree contains single null child then all its childrens are nulls.
-        // Replace all such elements will nulls, signifying that these are deleted named list elements.
-        prefixMap.replaceAll((key, value) ->
-            value instanceof Map && ((Map<?, ?>)value).containsValue(null) ? null : value
-        );
-
-        // Continue recursively.
-        for (Object value : prefixMap.values()) {
-            if (value instanceof Map)
-                compressDeletedEntries((Map<String, ?>)value);
-        }
-    }
 }
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 0a54d78..8785405 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
@@ -28,6 +28,7 @@ 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.annotation.PolymorphicConfigInstance;
 import org.apache.ignite.configuration.validation.Validator;
 import org.apache.ignite.internal.configuration.hocon.HoconConverter;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
@@ -51,6 +52,8 @@ public class ConfigurationManager implements IgniteComponent {
      * @param storage Configuration storage.
      * @param internalSchemaExtensions Internal extensions ({@link InternalConfiguration})
      *      of configuration schemas ({@link ConfigurationRoot} and {@link Config}).
+     * @param polymorphicSchemaExtensions Polymorphic extensions ({@link PolymorphicConfigInstance})
+     *      of configuration schemas.
      * @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.
      */
@@ -58,11 +61,18 @@ public class ConfigurationManager implements IgniteComponent {
         Collection<RootKey<?, ?>> rootKeys,
         Map<Class<? extends Annotation>, Set<Validator<? extends Annotation, ?>>> validators,
         ConfigurationStorage storage,
-        Collection<Class<?>> internalSchemaExtensions
+        Collection<Class<?>> internalSchemaExtensions,
+        Collection<Class<?>> polymorphicSchemaExtensions
     ) {
         checkConfigurationType(rootKeys, storage);
 
-        registry = new ConfigurationRegistry(rootKeys, validators, storage, internalSchemaExtensions);
+        registry = new ConfigurationRegistry(
+            rootKeys,
+            validators,
+            storage,
+            internalSchemaExtensions,
+            polymorphicSchemaExtensions
+        );
     }
 
     /** {@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 28e0544..5e6cd4a 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
@@ -29,6 +29,7 @@ import org.apache.ignite.configuration.notifications.ConfigurationListener;
 import org.apache.ignite.internal.configuration.tree.TraversableTreeNode;
 import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
 import org.apache.ignite.internal.configuration.util.KeyNotFoundException;
+import org.jetbrains.annotations.Nullable;
 
 import static java.util.Collections.unmodifiableCollection;
 
@@ -134,7 +135,7 @@ public abstract class ConfigurationNode<VIEW> implements ConfigurationProperty<V
 
             synchronized (this) {
                 if (cachedRootNode == oldRootNode) {
-                    beforeRefreshValue(newVal);
+                    beforeRefreshValue(newVal, val);
 
                     val = newVal;
 
@@ -172,8 +173,9 @@ public abstract class ConfigurationNode<VIEW> implements ConfigurationProperty<V
      * Callback from {@link #refreshValue()} that's called right before the update. Synchronized.
      *
      * @param newValue New configuration value.
+     * @param oldValue Old configuration value.
      */
-    protected void beforeRefreshValue(VIEW newValue) {
+    protected void beforeRefreshValue(VIEW newValue, @Nullable VIEW oldValue) {
         // No-op.
     }
 
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 5de9ade..08c5b23 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
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.configuration;
 
 import java.io.Serializable;
 import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -33,6 +34,8 @@ 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.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
 import org.apache.ignite.configuration.validation.ExceptKeys;
 import org.apache.ignite.configuration.validation.Immutable;
 import org.apache.ignite.configuration.validation.Max;
@@ -57,12 +60,18 @@ 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 java.util.stream.Collectors.toList;
 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;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicId;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicInstanceId;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicSchemaExtensions;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.schemaFields;
+import static org.apache.ignite.internal.util.CollectionUtils.difference;
+import static org.apache.ignite.internal.util.CollectionUtils.viewReadOnly;
 
 /** */
 public class ConfigurationRegistry implements IgniteComponent {
@@ -89,6 +98,8 @@ public class ConfigurationRegistry implements IgniteComponent {
      * @param storage Configuration storage.
      * @param internalSchemaExtensions Internal extensions ({@link InternalConfiguration})
      *      of configuration schemas ({@link ConfigurationRoot} and {@link Config}).
+     * @param polymorphicSchemaExtensions Polymorphic extensions ({@link PolymorphicConfigInstance})
+     *      of configuration schemas.
      * @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.
      */
@@ -96,23 +107,16 @@ public class ConfigurationRegistry implements IgniteComponent {
         Collection<RootKey<?, ?>> rootKeys,
         Map<Class<? extends Annotation>, Set<Validator<? extends Annotation, ?>>> validators,
         ConfigurationStorage storage,
-        Collection<Class<?>> internalSchemaExtensions
+        Collection<Class<?>> internalSchemaExtensions,
+        Collection<Class<?>> polymorphicSchemaExtensions
     ) {
         checkConfigurationType(rootKeys, storage);
 
-        Set<Class<?>> allSchemas = collectSchemas(rootKeys.stream().map(RootKey::schemaClass).collect(toSet()));
+        Set<Class<?>> allSchemas = collectSchemas(viewReadOnly(rootKeys, RootKey::schemaClass));
 
-        Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(internalSchemaExtensions);
+        Map<Class<?>, Set<Class<?>>> internalExtensions = internalExtensionsWithCheck(allSchemas, 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
-            );
-        }
+        Map<Class<?>, Set<Class<?>>> polymorphicExtensions = polymorphicExtensionsWithCheck(allSchemas, polymorphicSchemaExtensions);
 
         this.rootKeys = rootKeys;
 
@@ -132,7 +136,7 @@ public class ConfigurationRegistry implements IgniteComponent {
         };
 
         rootKeys.forEach(rootKey -> {
-            cgen.compileRootSchema(rootKey.schemaClass(), extensions);
+            cgen.compileRootSchema(rootKey.schemaClass(), internalExtensions, polymorphicExtensions);
 
             DynamicConfiguration<?, ?> cfg = cgen.instantiateCfg(rootKey, changer);
 
@@ -267,4 +271,115 @@ public class ConfigurationRegistry implements IgniteComponent {
 
         return CompletableFuture.allOf(resultFutures);
     }
+
+    /**
+     * Get configuration schemas and their validated internal extensions with checks.
+     *
+     * @param allSchemas All configuration schemas.
+     * @param internalSchemaExtensions Internal extensions ({@link InternalConfiguration})
+     *      of configuration schemas ({@link ConfigurationRoot} and {@link Config}).
+     * @return Mapping: original of the schema -> internal schema extensions.
+     * @throws IllegalArgumentException If the schema extension is invalid.
+     */
+    private Map<Class<?>, Set<Class<?>>> internalExtensionsWithCheck(
+        Set<Class<?>> allSchemas,
+        Collection<Class<?>> internalSchemaExtensions
+    ) {
+        if (internalSchemaExtensions.isEmpty())
+            return Map.of();
+
+        Map<Class<?>, Set<Class<?>>> internalExtensions = internalSchemaExtensions(internalSchemaExtensions);
+
+        Set<Class<?>> notInAllSchemas = difference(internalExtensions.keySet(), allSchemas);
+
+        if (!notInAllSchemas.isEmpty()) {
+            throw new IllegalArgumentException(
+                "Internal extensions for which no parent configuration schemas were found: " + notInAllSchemas
+            );
+        }
+
+        return internalExtensions;
+    }
+
+    /**
+     * Get polymorphic extensions of configuration schemas with checks.
+     *
+     * @param allSchemas All configuration schemas.
+     * @param polymorphicSchemaExtensions Polymorphic extensions ({@link PolymorphicConfigInstance})
+     *      of configuration schemas.
+     * @return Mapping: polymorphic scheme -> extensions (instances) of polymorphic configuration.
+     * @throws IllegalArgumentException If the schema extension is invalid.
+     */
+    private Map<Class<?>, Set<Class<?>>> polymorphicExtensionsWithCheck(
+        Set<Class<?>> allSchemas,
+        Collection<Class<?>> polymorphicSchemaExtensions
+    ) {
+        if (polymorphicSchemaExtensions.isEmpty())
+            return Map.of();
+
+        Map<Class<?>, Set<Class<?>>> polymorphicExtensions = polymorphicSchemaExtensions(polymorphicSchemaExtensions);
+
+        Set<Class<?>> notInAllSchemas = difference(polymorphicExtensions.keySet(), allSchemas);
+
+        if (!notInAllSchemas.isEmpty()) {
+            throw new IllegalArgumentException(
+                "Polymorphic extensions for which no polymorphic configuration schemas were found: " + notInAllSchemas
+            );
+        }
+
+        Collection<Class<?>> noPolymorphicExtensionsSchemas = allSchemas.stream()
+            .filter(ConfigurationUtil::isPolymorphicConfig)
+            .filter(not(polymorphicExtensions::containsKey))
+            .collect(toList());
+
+        if (!noPolymorphicExtensionsSchemas.isEmpty()) {
+            throw new IllegalArgumentException(
+                "Polymorphic configuration schemas for which no extensions were found: " + noPolymorphicExtensionsSchemas
+            );
+        }
+
+        checkPolymorphicConfigIds(polymorphicExtensions);
+
+        for (Map.Entry<Class<?>, Set<Class<?>>> e : polymorphicExtensions.entrySet()) {
+            Class<?> schemaClass = e.getKey();
+
+            Field typeIdField = schemaFields(schemaClass).get(0);
+
+            if (!isPolymorphicId(typeIdField)) {
+                throw new IllegalArgumentException(String.format(
+                    "First field in a polymorphic configuration schema must contain @%s: %s",
+                    PolymorphicId.class,
+                    schemaClass.getName()
+                ));
+            }
+        }
+
+        return polymorphicExtensions;
+    }
+
+    /**
+     * Checks that there are no conflicts between ids of a polymorphic configuration and its extensions (instances).
+     *
+     * @param polymorphicExtensions Mapping: polymorphic scheme -> extensions (instances) of polymorphic configuration.
+     * @throws IllegalArgumentException If a polymorphic configuration id conflict is found.
+     * @see PolymorphicConfigInstance#value
+     */
+    private void checkPolymorphicConfigIds(Map<Class<?>, Set<Class<?>>> polymorphicExtensions) {
+        // Mapping: id -> configuration schema.
+        Map<String, Class<?>> ids = new HashMap<>();
+
+        for (Map.Entry<Class<?>, Set<Class<?>>> e : polymorphicExtensions.entrySet()) {
+            for (Class<?> schemaClass : e.getValue()) {
+                String id = polymorphicInstanceId(schemaClass);
+                Class<?> prev = ids.put(id, schemaClass);
+
+                if (prev != null) {
+                    throw new IllegalArgumentException("Found an id conflict for a polymorphic configuration [id=" +
+                        id + ", schemas=" + List.of(prev, schemaClass));
+                }
+            }
+
+            ids.clear();
+        }
+    }
 }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationTreeWrapper.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationTreeWrapper.java
new file mode 100644
index 0000000..16bd4c4
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationTreeWrapper.java
@@ -0,0 +1,68 @@
+/*
+ * 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.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+import org.apache.ignite.configuration.ConfigurationTree;
+import org.apache.ignite.configuration.notifications.ConfigurationListener;
+
+/**
+ * {@link ConfigurationTree} wrapper.
+ *
+ * @param <VIEW> Value type of the node.
+ * @param <CHANGE> Type of the object that changes this node's value.
+ */
+public class ConfigurationTreeWrapper<VIEW, CHANGE> implements ConfigurationTree<VIEW, CHANGE> {
+    /** Configuration tree. */
+    protected final ConfigurationTree<VIEW, CHANGE> configTree;
+
+    /**
+     * Constructor.
+     *
+     * @param configTree Configuration tree.
+     */
+    public ConfigurationTreeWrapper(ConfigurationTree<VIEW, CHANGE> configTree) {
+        this.configTree = configTree;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String key() {
+        return configTree.key();
+    }
+
+    /** {@inheritDoc} */
+    @Override public VIEW value() {
+        return configTree.value();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void listen(ConfigurationListener<VIEW> listener) {
+        configTree.listen(listener);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void stopListen(ConfigurationListener<VIEW> listener) {
+        configTree.stopListen(listener);
+    }
+
+    /** {@inheritDoc} */
+    @Override public CompletableFuture<Void> change(Consumer<CHANGE> change) {
+        return configTree.change(change);
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DirectConfigurationTreeWrapper.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DirectConfigurationTreeWrapper.java
new file mode 100644
index 0000000..eea1e8f
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DirectConfigurationTreeWrapper.java
@@ -0,0 +1,46 @@
+/*
+ * 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.ConfigurationTree;
+import org.apache.ignite.configuration.DirectConfigurationProperty;
+
+/**
+ * {@link ConfigurationTree} wrapper with {@link DirectConfigurationProperty}.
+ *
+ * @param <VIEW> Value type of the node.
+ * @param <CHANGE> Type of the object that changes this node's value.
+ */
+public class DirectConfigurationTreeWrapper<VIEW, CHANGE> extends ConfigurationTreeWrapper<VIEW, CHANGE>
+    implements DirectConfigurationProperty<VIEW> {
+    /**
+     * Constructor.
+     *
+     * @param configTree Configuration tree.
+     */
+    public DirectConfigurationTreeWrapper(ConfigurationTree<VIEW, CHANGE> configTree) {
+        super(configTree);
+
+        assert configTree instanceof DirectConfigurationProperty : configTree;
+    }
+
+    /** {@inheritDoc} */
+    @Override public VIEW directValue() {
+        return ((DirectConfigurationProperty<VIEW>)configTree).directValue();
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DirectDynamicProperty.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DirectDynamicProperty.java
index 9393444..bb60780 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DirectDynamicProperty.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DirectDynamicProperty.java
@@ -39,15 +39,17 @@ public class DirectDynamicProperty<T extends Serializable>
      * @param rootKey Root key.
      * @param changer Configuration changer.
      * @param listenOnly Only adding listeners mode, without the ability to get or update the property value.
+     * @param readOnly Value cannot be changed.
      */
     public DirectDynamicProperty(
         List<String> prefix,
         String key,
         RootKey<?, ?> rootKey,
         DynamicConfigurationChanger changer,
-        boolean listenOnly
+        boolean listenOnly,
+        boolean readOnly
     ) {
-        super(prefix, key, rootKey, changer, listenOnly);
+        super(prefix, key, rootKey, changer, listenOnly, readOnly);
     }
 
     /** {@inheritDoc} */
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 59d5097..ba883a8 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
@@ -30,7 +30,9 @@ import org.apache.ignite.configuration.ConfigurationTree;
 import org.apache.ignite.configuration.RootKey;
 import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
 import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
+import org.apache.ignite.internal.configuration.tree.InnerNode;
 import org.apache.ignite.internal.configuration.util.ConfigurationNotificationsUtil;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * This class represents configuration root or node.
@@ -62,11 +64,12 @@ public abstract class DynamicConfiguration<VIEW, CHANGE> extends ConfigurationNo
 
     /**
      * Add new configuration member.
+     *
      * @param member Configuration member (leaf or node).
      * @param <P> Type of member.
      */
     protected final <P extends ConfigurationProperty<?>> void add(P member) {
-        members.put(member.key(), member);
+        addMember(members, member);
     }
 
     /** {@inheritDoc} */
@@ -84,8 +87,16 @@ public abstract class DynamicConfiguration<VIEW, CHANGE> extends ConfigurationNo
 
             /** {@inheritDoc} */
             @Override public void descend(ConstructableTreeNode node) {
-                if (level == keys.size())
-                    change.accept((CHANGE)node);
+                if (level == keys.size()) {
+                    if (node instanceof InnerNode) {
+                        // To support polymorphic configuration.
+                        change.accept(((InnerNode)node).specificNode());
+                    }
+                    else {
+                        // To support namedList configuration.
+                        change.accept((CHANGE)node);
+                    }
+                }
                 else
                     node.construct(keys.get(level++), this, true);
             }
@@ -106,8 +117,23 @@ public abstract class DynamicConfiguration<VIEW, CHANGE> extends ConfigurationNo
     }
 
     /** {@inheritDoc} */
-    @Override public final VIEW value() {
-        return refreshValue();
+    @Override public VIEW value() {
+        // To support polymorphic configuration.
+        return ((InnerNode)refreshValue()).specificNode();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeRefreshValue(VIEW newValue, @Nullable VIEW oldValue) {
+        if (oldValue == null || ((InnerNode)oldValue).schemaType() != ((InnerNode)newValue).schemaType()) {
+            Map<String, ConfigurationProperty<?>> newMembers = new LinkedHashMap<>(members);
+
+            if (oldValue != null)
+                removeMembers(oldValue, newMembers);
+
+            addMembers(newValue, newMembers);
+
+            members = newMembers;
+        }
     }
 
     /**
@@ -142,8 +168,68 @@ public abstract class DynamicConfiguration<VIEW, CHANGE> extends ConfigurationNo
     }
 
     /**
+     * Returns configuration interface.
+     *
      * @return Configuration interface, for example {@code RootConfiguration}.
      * @throws UnsupportedOperationException In the case of a named list.
      */
     public abstract Class<? extends ConfigurationProperty<VIEW>> configType();
+
+    /**
+     * Returns specific configuration tree.
+     *
+     * @return Specific configuration tree.
+     */
+    public ConfigurationTree<VIEW, CHANGE> specificConfigTree() {
+        // To work with polymorphic configuration.
+        return this;
+    }
+
+    /**
+     * Removes members of the previous instance of polymorphic configuration.
+     *
+     * @param oldValue Old configuration value.
+     * @param members Configuration members (leaves and nodes).
+     */
+    protected void removeMembers(VIEW oldValue, Map<String, ConfigurationProperty<?>> members) {
+        // No-op.
+    }
+
+    /**
+     * Adds members of the previous instance of polymorphic configuration.
+     *
+     * @param newValue New configuration value.
+     * @param members Configuration members (leaves and nodes).
+     */
+    protected void addMembers(VIEW newValue, Map<String, ConfigurationProperty<?>> members) {
+        // No-op.
+    }
+
+    /**
+     * Add configuration member.
+     *
+     * @param members Configuration members (leaves and nodes).
+     * @param member Configuration member (leaf or node).
+     * @param <P> Type of member.
+     */
+    protected <P extends ConfigurationProperty<?>> void addMember(
+        Map<String, ConfigurationProperty<?>> members,
+        P member
+    ) {
+        members.put(member.key(), member);
+    }
+
+    /**
+     * Remove configuration member.
+     *
+     * @param members Configuration members (leaves and nodes).
+     * @param member Configuration member (leaf or node).
+     * @param <P> Type of member.
+     */
+    protected <P extends ConfigurationProperty<?>> void removeMember(
+        Map<String, ConfigurationProperty<?>> members,
+        P member
+    ) {
+        members.remove(member.key());
+    }
 }
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 925cd09..4b31db4 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
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.Objects;
 import java.util.RandomAccess;
 import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.configuration.ConfigurationReadOnlyException;
 import org.apache.ignite.configuration.ConfigurationValue;
 import org.apache.ignite.configuration.RootKey;
 import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
@@ -33,6 +34,9 @@ import org.apache.ignite.internal.tostring.S;
  * Expected to be used with numbers, strings and other immutable objects, e.g. IP addresses.
  */
 public class DynamicProperty<T extends Serializable> extends ConfigurationNode<T> implements ConfigurationValue<T> {
+    /** Value cannot be changed. */
+    private final boolean readOnly;
+
     /**
      * Constructor.
      *
@@ -41,15 +45,19 @@ public class DynamicProperty<T extends Serializable> extends ConfigurationNode<T
      * @param rootKey Root key.
      * @param changer Configuration changer.
      * @param listenOnly Only adding listeners mode, without the ability to get or update the property value.
+     * @param readOnly Value cannot be changed.
      */
     public DynamicProperty(
         List<String> prefix,
         String key,
         RootKey<?, ?> rootKey,
         DynamicConfigurationChanger changer,
-        boolean listenOnly
+        boolean listenOnly,
+        boolean readOnly
     ) {
         super(prefix, key, rootKey, changer, listenOnly);
+
+        this.readOnly = readOnly;
     }
 
     /** {@inheritDoc} */
@@ -64,6 +72,9 @@ public class DynamicProperty<T extends Serializable> extends ConfigurationNode<T
         if (listenOnly)
             throw listenOnlyException();
 
+        if (readOnly)
+            throw new ConfigurationReadOnlyException("Read only mode: " + keys);
+
         assert keys instanceof RandomAccess;
         assert !keys.isEmpty();
 
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/NamedListConfiguration.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/NamedListConfiguration.java
index cfac2b1..cf91ac6 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/NamedListConfiguration.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/NamedListConfiguration.java
@@ -29,6 +29,7 @@ import org.apache.ignite.configuration.NamedListChange;
 import org.apache.ignite.configuration.NamedListView;
 import org.apache.ignite.configuration.RootKey;
 import org.apache.ignite.configuration.notifications.ConfigurationNamedListListener;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Named configuration wrapper.
@@ -79,11 +80,16 @@ public class NamedListConfiguration<T extends ConfigurationProperty<VIEW>, VIEW,
     @Override public T get(String name) {
         refreshValue();
 
-        return (T)members.get(name);
+        ConfigurationProperty<?> configProperty = members.get(name);
+
+        return configProperty == null ? null : (T)((DynamicConfiguration<?, ?>)configProperty).specificConfigTree();
     }
 
     /** {@inheritDoc} */
-    @Override protected synchronized void beforeRefreshValue(NamedListView<VIEW> newValue) {
+    @Override protected synchronized void beforeRefreshValue(
+        NamedListView<VIEW> newValue,
+        @Nullable NamedListView<VIEW> oldValue
+    ) {
         Map<String, ConfigurationProperty<?>> oldValues = this.members;
         Map<String, ConfigurationProperty<?>> newValues = new LinkedHashMap<>();
 
@@ -134,4 +140,9 @@ public class NamedListConfiguration<T extends ConfigurationProperty<VIEW>, VIEW,
     @Override public Class<? extends ConfigurationProperty<NamedListView<VIEW>>> configType() {
         throw new UnsupportedOperationException("Not supported.");
     }
+
+    /** {@inheritDoc} */
+    @Override public NamedListView<VIEW> value() {
+        return refreshValue();
+    }
 }
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 03edebe..f318482 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
@@ -27,11 +27,11 @@ import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
@@ -40,6 +40,7 @@ import java.util.Queue;
 import java.util.Set;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
 import com.facebook.presto.bytecode.BytecodeBlock;
@@ -53,7 +54,9 @@ import com.facebook.presto.bytecode.Variable;
 import com.facebook.presto.bytecode.control.IfStatement;
 import com.facebook.presto.bytecode.expression.BytecodeExpression;
 import org.apache.ignite.configuration.ConfigurationProperty;
+import org.apache.ignite.configuration.ConfigurationTree;
 import org.apache.ignite.configuration.ConfigurationValue;
+import org.apache.ignite.configuration.ConfigurationWrongPolymorphicTypeIdException;
 import org.apache.ignite.configuration.DirectConfigurationProperty;
 import org.apache.ignite.configuration.NamedConfigurationTree;
 import org.apache.ignite.configuration.NamedListView;
@@ -63,7 +66,12 @@ import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.annotation.DirectAccess;
 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.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
+import org.apache.ignite.internal.configuration.ConfigurationNode;
+import org.apache.ignite.internal.configuration.ConfigurationTreeWrapper;
+import org.apache.ignite.internal.configuration.DirectConfigurationTreeWrapper;
 import org.apache.ignite.internal.configuration.DirectDynamicConfiguration;
 import org.apache.ignite.internal.configuration.DirectDynamicProperty;
 import org.apache.ignite.internal.configuration.DirectNamedListConfiguration;
@@ -77,7 +85,9 @@ import org.apache.ignite.internal.configuration.tree.ConfigurationVisitor;
 import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
 import org.apache.ignite.internal.configuration.tree.InnerNode;
 import org.apache.ignite.internal.configuration.tree.NamedListNode;
+import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
@@ -98,20 +108,32 @@ import static com.facebook.presto.bytecode.expression.BytecodeExpressions.consta
 import static com.facebook.presto.bytecode.expression.BytecodeExpressions.inlineIf;
 import static com.facebook.presto.bytecode.expression.BytecodeExpressions.invokeDynamic;
 import static com.facebook.presto.bytecode.expression.BytecodeExpressions.invokeStatic;
+import static com.facebook.presto.bytecode.expression.BytecodeExpressions.isNotNull;
 import static com.facebook.presto.bytecode.expression.BytecodeExpressions.isNull;
 import static com.facebook.presto.bytecode.expression.BytecodeExpressions.newInstance;
+import static com.facebook.presto.bytecode.expression.BytecodeExpressions.not;
 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.function.Function.identity;
+import static java.util.stream.Collectors.toCollection;
 import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
 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.hasDefault;
 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.isPolymorphicConfig;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicConfigInstance;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicId;
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.isValue;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicInstanceId;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.schemaFields;
+import static org.apache.ignite.internal.util.ArrayUtils.nullOrEmpty;
 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;
@@ -124,10 +146,16 @@ import static org.objectweb.asm.Type.getType;
  * from {@code bytecode} module to achieve this goal, like {@link ClassGenerator}, for examples.
  */
 public class ConfigurationAsmGenerator {
+    /** {@link DynamicConfiguration#DynamicConfiguration} constructor. */
+    private static final Constructor<?> DYNAMIC_CONFIGURATION_CTOR;
+
+    /** {@link DirectDynamicConfiguration#DirectDynamicConfiguration} constructor. */
+    private static final Constructor<?> DIRECT_DYNAMIC_CONFIGURATION_CTOR;
+
     /** {@link LambdaMetafactory#metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)} */
     private static final Method LAMBDA_METAFACTORY;
 
-    /** {@link Consumer#accept(Object)}*/
+    /** {@link Consumer#accept(Object)} */
     private static final Method ACCEPT;
 
     /** {@link ConfigurationVisitor#visitLeafNode(String, Serializable)} */
@@ -148,18 +176,45 @@ public class ConfigurationAsmGenerator {
     /** {@link ConstructableTreeNode#copy()} */
     private static final Method COPY;
 
-    /** {@link DynamicConfiguration#DynamicConfiguration} */
-    private static final Constructor<?> DYNAMIC_CONFIGURATION_CTOR;
-
-    /** {@link DirectDynamicConfiguration#DirectDynamicConfiguration} */
-    private static final Constructor<?> DIRECT_DYNAMIC_CONFIGURATION_CTOR;
-
-    /** {@link DynamicConfiguration#add(ConfigurationProperty)} */
-    private static final Method DYNAMIC_CONFIGURATION_ADD;
+    /** {@code DynamicConfiguration#add} method. */
+    private static final Method DYNAMIC_CONFIGURATION_ADD_MTD;
 
     /** {@link Objects#requireNonNull(Object, String)} */
     private static final Method REQUIRE_NON_NULL;
 
+    /** {@link Class#getName} method. */
+    private static final Method CLASS_GET_NAME_MTD;
+
+    /** {@link String#equals} method. */
+    private static final Method STRING_EQUALS_MTD;
+
+    /** {@link ConfigurationSource#polymorphicTypeId} method. */
+    private static final Method POLYMORPHIC_TYPE_ID_MTD;
+
+    /** {@link InnerNode#constructDefault} method. */
+    private static final Method CONSTRUCT_DEFAULT_MTD;
+
+    /** {@code ConfigurationNode#refreshValue} method. */
+    private static final Method REFRESH_VALUE_MTD;
+
+    /** {@code DynamicConfiguration#addMember} method. */
+    private static final Method ADD_MEMBER_MTD;
+
+    /** {@code DynamicConfiguration#removeMember} method. */
+    private static final Method REMOVE_MEMBER_MTD;
+
+    /** {@link InnerNode#specificNode} method. */
+    private static final Method SPECIFIC_NODE_MTD;
+
+    /** {@link DynamicConfiguration#specificConfigTree} method. */
+    private static final Method SPECIFIC_CONFIG_TREE_MTD;
+
+    /** {@link ConfigurationUtil#addDefaults}. */
+    private static final Method ADD_DEFAULTS_MTD;
+
+    /** {@code Node#convert} method name. */
+    private static final String CONVERT_MTD_NAME = "convert";
+
     static {
         try {
             LAMBDA_METAFACTORY = LambdaMetafactory.class.getDeclaredMethod(
@@ -205,12 +260,32 @@ public class ConfigurationAsmGenerator {
                 boolean.class
             );
 
-            DYNAMIC_CONFIGURATION_ADD = DynamicConfiguration.class.getDeclaredMethod(
+            DYNAMIC_CONFIGURATION_ADD_MTD = DynamicConfiguration.class.getDeclaredMethod(
                 "add",
                 ConfigurationProperty.class
             );
 
             REQUIRE_NON_NULL = Objects.class.getDeclaredMethod("requireNonNull", Object.class, String.class);
+
+            CLASS_GET_NAME_MTD = Class.class.getDeclaredMethod("getName");
+
+            STRING_EQUALS_MTD = String.class.getDeclaredMethod("equals", Object.class);
+
+            POLYMORPHIC_TYPE_ID_MTD = ConfigurationSource.class.getDeclaredMethod("polymorphicTypeId", String.class);
+
+            CONSTRUCT_DEFAULT_MTD = InnerNode.class.getDeclaredMethod("constructDefault", String.class);
+
+            REFRESH_VALUE_MTD = ConfigurationNode.class.getDeclaredMethod("refreshValue");
+
+            ADD_MEMBER_MTD = DynamicConfiguration.class.getDeclaredMethod("addMember", Map.class, ConfigurationProperty.class);
+
+            REMOVE_MEMBER_MTD = DynamicConfiguration.class.getDeclaredMethod("removeMember", Map.class, ConfigurationProperty.class);
+
+            SPECIFIC_NODE_MTD = InnerNode.class.getDeclaredMethod("specificNode");
+
+            SPECIFIC_CONFIG_TREE_MTD = DynamicConfiguration.class.getDeclaredMethod("specificConfigTree");
+
+            ADD_DEFAULTS_MTD = ConfigurationUtil.class.getDeclaredMethod("addDefaults", InnerNode.class);
         }
         catch (NoSuchMethodException nsme) {
             throw new ExceptionInInitializerError(nsme);
@@ -225,6 +300,7 @@ public class ConfigurationAsmGenerator {
 
     /**
      * Creates new instance of {@code *Node} class corresponding to the given Configuration Schema.
+     *
      * @param schemaClass Configuration Schema class.
      * @return Node instance.
      */
@@ -247,6 +323,7 @@ public class ConfigurationAsmGenerator {
 
     /**
      * Creates new instance of {@code *Configuration} class corresponding to the given Configuration Schema.
+     *
      * @param rootKey Root key of the configuration root.
      * @param changer Configuration changer instance to pass into constructor.
      * @return Configuration instance.
@@ -280,14 +357,18 @@ 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.
-     * @param internalSchemaExtensions Internal extensions ({@link InternalConfiguration})
-     *      of configuration schemas ({@link ConfigurationRoot} and {@link Config}).
-     *      Mapping: original schema -> extensions.
+     * @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.
+     * @param polymorphicSchemaExtensions Polymorphic extensions ({@link PolymorphicConfigInstance})
+     *                                    of configuration schemas ({@link PolymorphicConfig}).
+     *                                    Mapping: original schema -> extensions.
      */
     public synchronized void compileRootSchema(
         Class<?> rootSchemaClass,
-        Map<Class<?>, Set<Class<?>>> internalSchemaExtensions
+        Map<Class<?>, Set<Class<?>>> internalSchemaExtensions,
+        Map<Class<?>, Set<Class<?>>> polymorphicSchemaExtensions
     ) {
         if (schemasInfo.containsKey(rootSchemaClass))
             return; // Already compiled.
@@ -298,25 +379,29 @@ public class ConfigurationAsmGenerator {
         schemasInfo.put(rootSchemaClass, new SchemaClassesInfo(rootSchemaClass));
 
         Set<Class<?>> schemas = new HashSet<>();
-        List<ClassDefinition> definitions = new ArrayList<>();
+        List<ClassDefinition> classDefs = new ArrayList<>();
 
         while (!compileQueue.isEmpty()) {
             Class<?> schemaClass = compileQueue.poll();
 
             assert schemaClass.isAnnotationPresent(ConfigurationRoot.class)
                 || schemaClass.isAnnotationPresent(Config.class)
+                || isPolymorphicConfig(schemaClass)
                 : schemaClass + " is not properly annotated";
 
             assert schemasInfo.containsKey(schemaClass) : schemaClass;
 
-            Field[] schemaFields = Arrays.stream(schemaClass.getDeclaredFields()).filter(
-                field -> isValue(field) || isConfigValue(field) || isNamedConfigValue(field)
-            ).toArray(Field[]::new);
+            Set<Class<?>> internalExtensions = internalSchemaExtensions.getOrDefault(schemaClass, Set.of());
+            Set<Class<?>> polymorphicExtensions = polymorphicSchemaExtensions.getOrDefault(schemaClass, Set.of());
+
+            assert internalExtensions.isEmpty() || polymorphicExtensions.isEmpty() :
+                "Internal and polymorphic extensions are not allowed at the same time: " + schemaClass;
 
-            Set<Class<?>> schemaExtensions = internalSchemaExtensions.getOrDefault(schemaClass, Set.of());
-            Set<Field> extensionsFields = extensionsFields(schemaExtensions);
+            List<Field> schemaFields = schemaFields(schemaClass);
+            Collection<Field> internalExtensionsFields = extensionsFields(internalExtensions, true);
+            Collection<Field> polymorphicExtensionsFields = extensionsFields(polymorphicExtensions, false);
 
-            for (Field schemaField : concat(asList(schemaFields), extensionsFields)) {
+            for (Field schemaField : concat(schemaFields, internalExtensionsFields, polymorphicExtensionsFields)) {
                 if (isConfigValue(schemaField) || isNamedConfigValue(schemaField)) {
                     Class<?> subSchemaClass = schemaField.getType();
 
@@ -328,12 +413,58 @@ public class ConfigurationAsmGenerator {
                 }
             }
 
+            for (Class<?> polymorphicExtension : polymorphicExtensions)
+                schemasInfo.put(polymorphicExtension, new SchemaClassesInfo(polymorphicExtension));
+
             schemas.add(schemaClass);
-            definitions.add(createNodeClass(schemaClass, schemaExtensions, schemaFields, extensionsFields));
-            definitions.add(createCfgImplClass(schemaClass, schemaExtensions, schemaFields, extensionsFields));
+
+            ClassDefinition innerNodeClassDef = createNodeClass(
+                schemaClass,
+                internalExtensions,
+                polymorphicExtensions,
+                schemaFields,
+                internalExtensionsFields,
+                polymorphicExtensionsFields
+            );
+
+            classDefs.add(innerNodeClassDef);
+
+            ClassDefinition cfgImplClassDef = createCfgImplClass(
+                schemaClass,
+                internalExtensions,
+                polymorphicExtensions,
+                schemaFields,
+                internalExtensionsFields,
+                polymorphicExtensionsFields
+            );
+
+            classDefs.add(cfgImplClassDef);
+
+            for (Class<?> polymorphicExtension : polymorphicExtensions) {
+                // Only the fields of a specific instance of a polymorphic configuration.
+                Collection<Field> polymorphicFields = polymorphicExtensionsFields.stream()
+                    .filter(f -> f.getDeclaringClass() == polymorphicExtension)
+                    .collect(toList());
+
+                classDefs.add(createPolymorphicExtensionNodeClass(
+                    schemaClass,
+                    polymorphicExtension,
+                    innerNodeClassDef,
+                    schemaFields,
+                    polymorphicFields
+                ));
+
+                classDefs.add(createPolymorphicExtensionCfgImplClass(
+                    schemaClass,
+                    polymorphicExtension,
+                    cfgImplClassDef,
+                    schemaFields,
+                    polymorphicFields
+                ));
+            }
         }
 
-        Map<String, Class<?>> definedClasses = generator.defineClasses(definitions);
+        Map<String, Class<?>> definedClasses = generator.defineClasses(classDefs);
 
         for (Class<?> schemaClass : schemas) {
             SchemaClassesInfo info = schemasInfo.get(schemaClass);
@@ -346,17 +477,21 @@ public class ConfigurationAsmGenerator {
     /**
      * 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.
+     * @param schemaClass           Configuration schema class.
+     * @param internalExtensions    Internal extensions of the configuration schema.
+     * @param polymorphicExtensions Polymorphic extensions of the configuration schema.
+     * @param schemaFields          Fields of the schema class.
+     * @param internalFields        Fields of internal extensions of the configuration schema.
+     * @param polymorphicFields     Fields of polymorphic 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
+        Set<Class<?>> internalExtensions,
+        Set<Class<?>> polymorphicExtensions,
+        List<Field> schemaFields,
+        Collection<Field> internalFields,
+        Collection<Field> polymorphicFields
     ) {
         SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
 
@@ -365,7 +500,7 @@ public class ConfigurationAsmGenerator {
             of(PUBLIC, FINAL),
             internalName(schemaClassInfo.nodeClassName),
             type(InnerNode.class),
-            nodeClassInterfaces(schemaClass, schemaExtensions)
+            nodeClassInterfaces(schemaClass, internalExtensions)
         );
 
         // Spec fields.
@@ -373,69 +508,176 @@ public class ConfigurationAsmGenerator {
 
         int i = 0;
 
-        for (Class<?> clazz : concat(List.of(schemaClass), schemaExtensions))
+        for (Class<?> clazz : concat(List.of(schemaClass), internalExtensions, polymorphicExtensions))
             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 : concat(asList(schemaFields), extensionsFields)) {
-            assert isValue(schemaField) || isConfigValue(schemaField) || isNamedConfigValue(schemaField) : schemaField;
+        // To store the id of the polymorphic configuration instance.
+        FieldDefinition polymorphicTypeIdFieldDef = null;
 
-            fieldDefs.put(schemaField.getName(), addNodeField(classDef, schemaField));
+        for (Field schemaField : concat(schemaFields, internalFields, polymorphicFields)) {
+            String fieldName = fieldName(schemaField);
+
+            FieldDefinition fieldDef = addNodeField(classDef, schemaField, fieldName);
+
+            fieldDefs.put(fieldName, fieldDef);
+
+            if (isPolymorphicId(schemaField))
+                polymorphicTypeIdFieldDef = fieldDef;
         }
 
+        // org.apache.ignite.internal.configuration.tree.InnerNode#schemaType
+        addNodeSchemaTypeMethod(classDef, schemaClass, polymorphicExtensions, polymorphicTypeIdFieldDef);
+
         // Constructor.
-        addNodeConstructor(classDef, specFields, fieldDefs, schemaFields, extensionsFields);
+        addNodeConstructor(classDef, specFields, fieldDefs, schemaFields, internalFields, polymorphicFields);
 
         // VIEW and CHANGE methods.
-        for (Field schemaField : concat(asList(schemaFields), extensionsFields)) {
+        for (Field schemaField : concat(schemaFields, internalFields)) {
             String fieldName = schemaField.getName();
 
             FieldDefinition fieldDef = fieldDefs.get(fieldName);
 
-            addNodeViewMethod(classDef, schemaField, fieldDef);
+            addNodeViewMethod(
+                classDef,
+                schemaField,
+                viewMtd -> getThisFieldCode(viewMtd, fieldDef),
+                null
+            );
+
+            // Read only.
+            if (isPolymorphicId(schemaField))
+                continue;
 
             // Add change methods.
-            MethodDefinition changeMtd =
-                addNodeChangeMethod(classDef, schemaField, fieldDef, schemaClassInfo.nodeClassName);
-            addNodeChangeBridgeMethod(classDef, changeClassName(schemaField.getDeclaringClass()), changeMtd);
+            MethodDefinition changeMtd0 = addNodeChangeMethod(
+                classDef,
+                schemaField,
+                changeMtd -> getThisFieldCode(changeMtd, fieldDef),
+                (changeMtd, newValue) -> setThisFieldCode(changeMtd, newValue, fieldDef),
+                null
+            );
+
+            addNodeChangeBridgeMethod(classDef, changeClassName(schemaField.getDeclaringClass()), changeMtd0);
+        }
+
+        Map<Class<?>, List<Field>> polymorphicFieldsByExtension = Map.of();
+
+        MethodDefinition changePolymorphicTypeIdMtd = null;
+
+        if (!polymorphicExtensions.isEmpty()) {
+            assert polymorphicTypeIdFieldDef != null : schemaClass.getName();
+
+            addNodeSpecificNodeMethod(classDef, polymorphicExtensions, polymorphicTypeIdFieldDef);
+
+            changePolymorphicTypeIdMtd = addNodeChangePolymorphicTypeIdMethod(
+                classDef,
+                fieldDefs,
+                polymorphicExtensions,
+                polymorphicFields,
+                polymorphicTypeIdFieldDef
+            );
+
+            addNodeConvertMethod(classDef, schemaClass, polymorphicExtensions, changePolymorphicTypeIdMtd);
+
+            polymorphicFieldsByExtension = new LinkedHashMap<>();
+
+            for (Class<?> polymorphicExtension : polymorphicExtensions) {
+                polymorphicFieldsByExtension.put(
+                    polymorphicExtension,
+                    polymorphicFields.stream()
+                        .filter(f -> polymorphicExtension.equals(f.getDeclaringClass()))
+                        .collect(toList())
+                );
+            }
         }
 
         // traverseChildren
-        addNodeTraverseChildrenMethod(classDef, fieldDefs, schemaFields, extensionsFields);
+        addNodeTraverseChildrenMethod(
+            classDef,
+            schemaClass,
+            fieldDefs,
+            schemaFields,
+            internalFields,
+            polymorphicFieldsByExtension,
+            polymorphicTypeIdFieldDef
+        );
 
         // traverseChild
-        addNodeTraverseChildMethod(classDef, fieldDefs, schemaFields, extensionsFields);
+        addNodeTraverseChildMethod(
+            classDef,
+            fieldDefs,
+            schemaFields,
+            internalFields,
+            polymorphicFieldsByExtension,
+            polymorphicTypeIdFieldDef
+        );
 
         // construct
-        addNodeConstructMethod(classDef, fieldDefs, schemaFields, extensionsFields);
+        addNodeConstructMethod(
+            classDef,
+            fieldDefs,
+            schemaFields,
+            internalFields,
+            polymorphicFieldsByExtension,
+            polymorphicTypeIdFieldDef,
+            changePolymorphicTypeIdMtd
+        );
 
         // constructDefault
-        addNodeConstructDefaultMethod(classDef, specFields, fieldDefs, schemaFields, extensionsFields);
+        addNodeConstructDefaultMethod(
+            classDef,
+            specFields,
+            fieldDefs,
+            schemaFields,
+            internalFields,
+            polymorphicFieldsByExtension,
+            polymorphicTypeIdFieldDef
+        );
 
         return classDef;
     }
 
     /**
-     * Add {@link InnerNode#schemaType()} method implementation to the class. It looks like the following code:
-     * <pre>{@code
-     * public Class schemaType() {
-     *     return this._spec.getClass();
-     * }
-     * }</pre>
-     * @param classDef Class definition.
-     * @param specField Field definition of the {@code _spec} field.
+     * Add {@link InnerNode#schemaType} method implementation to the class.
+     *
+     * @param classDef                  Class definition.
+     * @param schemaClass               Configuration schema class.
+     * @param polymorphicExtensions     Polymorphic extensions of the configuration schema.
+     * @param polymorphicTypeIdFieldDef Identification field for the polymorphic configuration instance.
      */
-    private static void addNodeSchemaTypeMethod(ClassDefinition classDef, FieldDefinition specField) {
-        MethodDefinition schemaTypeMtd = classDef.declareMethod(of(PUBLIC), "schemaType", type(Class.class));
+    private static void addNodeSchemaTypeMethod(
+        ClassDefinition classDef,
+        Class<?> schemaClass,
+        Set<Class<?>> polymorphicExtensions,
+        @Nullable FieldDefinition polymorphicTypeIdFieldDef
+    ) {
+        MethodDefinition schemaTypeMtd = classDef.declareMethod(
+            of(PUBLIC),
+            "schemaType",
+            type(Class.class)
+        );
+
+        BytecodeBlock mtdBody = schemaTypeMtd.getBody();
+
+        if (polymorphicExtensions.isEmpty())
+            mtdBody.append(constantClass(schemaClass)).retObject();
+        else {
+            assert polymorphicTypeIdFieldDef != null : classDef.getName();
+
+            StringSwitchBuilder switchBuilderTypeId = typeIdSwitchBuilder(schemaTypeMtd, polymorphicTypeIdFieldDef);
+
+            for (Class<?> polymorphicExtension : polymorphicExtensions) {
+                switchBuilderTypeId.addCase(
+                    polymorphicInstanceId(polymorphicExtension),
+                    constantClass(polymorphicExtension).ret()
+                );
+            }
 
-        schemaTypeMtd.getBody().append(
-            schemaTypeMtd.getThis().getField(specField).invoke("getClass", Class.class)
-        ).retObject();
+            mtdBody.append(switchBuilderTypeId.build());
+        }
     }
 
     /**
@@ -443,80 +685,81 @@ public class ConfigurationAsmGenerator {
      * <ul>
      *     <li>
      *         {@code @Value public type fieldName}<br/>becomes<br/>
-     *         {@code private BoxedType fieldName}
+     *         {@code public BoxedType fieldName}
      *     </li>
      *     <li>
      *         {@code @ConfigValue public MyConfigurationSchema fieldName}<br/>becomes<br/>
-     *         {@code private MyNode fieldName}
+     *         {@code public MyNode fieldName}
      *     </li>
      *     <li>
      *         {@code @NamedConfigValue public type fieldName}<br/>becomes<br/>
-     *         {@code private NamedListNode fieldName}
+     *         {@code public NamedListNode fieldName}
+     *     </li>
+     *     <li>
+     *         {@code @PolymorphicId public String fieldName}<br/>becomes<br/>
+     *         {@code public String fieldName}
      *     </li>
      * </ul>
-     * @param classDef Node class definition.
+     *
+     * @param classDef    Node class definition.
      * @param schemaField Configuration Schema class field.
+     * @param fieldName   Field name.
      * @return Declared field definition.
      */
-    private FieldDefinition addNodeField(ClassDefinition classDef, Field schemaField) {
+    private FieldDefinition addNodeField(ClassDefinition classDef, Field schemaField, String fieldName) {
         Class<?> schemaFieldClass = schemaField.getType();
 
         ParameterizedType nodeFieldType;
 
-        if (isValue(schemaField))
+        if (isValue(schemaField) || isPolymorphicId(schemaField))
             nodeFieldType = type(box(schemaFieldClass));
         else if (isConfigValue(schemaField))
             nodeFieldType = typeFromJavaClassName(schemasInfo.get(schemaFieldClass).nodeClassName);
         else
             nodeFieldType = type(NamedListNode.class);
 
-        return classDef.declareField(of(PRIVATE), schemaField.getName(), nodeFieldType);
+        return classDef.declareField(of(PUBLIC), fieldName, nodeFieldType);
     }
 
     /**
      * 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 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.
+     * @param classDef          Node class definition.
+     * @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 internalFields    Fields of internal extensions of the configuration schema.
+     * @param polymorphicFields Fields of polymorphic extensions of the configuration schema.
      */
     private void addNodeConstructor(
         ClassDefinition classDef,
         Map<Class<?>, FieldDefinition> specFields,
         Map<String, FieldDefinition> fieldDefs,
-        Field[] schemaFields,
-        Set<Field> extensionsFields
+        Collection<Field> schemaFields,
+        Collection<Field> internalFields,
+        Collection<Field> polymorphicFields
     ) {
         MethodDefinition ctor = classDef.declareConstructor(of(PUBLIC));
 
         // super();
-        ctor.getBody().append(ctor.getThis()).invokeConstructor(InnerNode.class);
+        ctor.getBody()
+            .append(ctor.getThis())
+            .invokeConstructor(InnerNode.class);
 
         // 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 : concat(asList(schemaFields), extensionsFields)) {
+        for (Field schemaField : concat(schemaFields, internalFields, polymorphicFields)) {
             if (!isNamedConfigValue(schemaField))
                 continue;
 
-            NamedConfigValue namedCfgAnnotation = schemaField.getAnnotation(NamedConfigValue.class);
-
-            SchemaClassesInfo fieldClassNames = schemasInfo.get(schemaField.getType());
+            FieldDefinition fieldDef = fieldDefs.get(fieldName(schemaField));
 
-            // this.values = new NamedListNode<>(key, ValueNode::new);
-            ctor.getBody().append(ctor.getThis().setField(
-                fieldDefs.get(schemaField.getName()),
-                newInstance(
-                    NamedListNode.class,
-                    constantString(namedCfgAnnotation.syntheticKeyName()),
-                    newNamedListElementLambda(fieldClassNames.nodeClassName)
-                )
-            ));
+            // this.values = new NamedListNode<>(key, ValueNode::new, "polymorphicIdFieldName");
+            ctor.getBody().append(setThisFieldCode(ctor, newNamedListNode(schemaField), fieldDef));
         }
 
         // return;
@@ -524,24 +767,32 @@ public class ConfigurationAsmGenerator {
     }
 
     /**
-     * Implements getter method from {@code VIEW} interface. It returns field value, possibly unboxed or cloned,
-     * depending on type.
-     * @param classDef Node class definition.
-     * @param schemaField Configuration Schema class field.
-     * @param fieldDef Field definition.
+     * Implements getter method from {@code VIEW} interface.
+     * It returns field value, possibly unboxed or cloned, depending on type.
+     *
+     * @param classDef                     Node class definition.
+     * @param schemaField                  Configuration Schema class field.
+     * @param getFieldCodeFun              Function for creating bytecode to get a field,
+     *                                     for example: {@code this.field} or {@code this.field.field}.
+     * @param getPolymorphicTypeIdFieldFun Function for creating bytecode to get the field that stores the identifier
+     *                                     of the polymorphic configuration instance is needed to add a polymorphicTypeId check,
+     *                                     for example: {@code this.typeId} or {@code this.field.typeId}.
      */
     private void addNodeViewMethod(
         ClassDefinition classDef,
         Field schemaField,
-        FieldDefinition fieldDef
+        Function<MethodDefinition, BytecodeExpression> getFieldCodeFun,
+        @Nullable Function<MethodDefinition, BytecodeExpression> getPolymorphicTypeIdFieldFun
     ) {
         Class<?> schemaFieldType = schemaField.getType();
 
         ParameterizedType returnType;
 
+        SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaFieldType);
+
         // Return type is either corresponding VIEW type or the same type as declared in schema.
         if (isConfigValue(schemaField))
-            returnType = typeFromJavaClassName(schemasInfo.get(schemaFieldType).viewClassName);
+            returnType = typeFromJavaClassName(schemaClassInfo.viewClassName);
         else if (isNamedConfigValue(schemaField))
             returnType = type(NamedListView.class);
         else
@@ -555,86 +806,152 @@ public class ConfigurationAsmGenerator {
             returnType
         );
 
-        // result = this.field;
-        viewMtd.getBody().append(viewMtd.getThis().getField(fieldDef));
+        BytecodeBlock bytecodeBlock = new BytecodeBlock();
+
+        // result = this.field; OR this.field.field.
+        bytecodeBlock.append(getFieldCodeFun.apply(viewMtd));
 
-        // result = Box.boxValue(result); // Unboxing.
         if (schemaFieldType.isPrimitive()) {
-            viewMtd.getBody().invokeVirtual(
+            // result = Box.boxValue(result); // Unboxing.
+            bytecodeBlock.invokeVirtual(
                 box(schemaFieldType),
                 schemaFieldType.getSimpleName() + "Value",
                 schemaFieldType
             );
         }
-
-        // retuls = result.clone();
-        if (schemaFieldType.isArray())
-            viewMtd.getBody().invokeVirtual(schemaFieldType, "clone", Object.class).checkCast(schemaFieldType);
+        else if (schemaFieldType.isArray()) {
+            // result = result.clone();
+            bytecodeBlock.invokeVirtual(schemaFieldType, "clone", Object.class).checkCast(schemaFieldType);
+        }
+        else if (isPolymorphicConfig(schemaFieldType) && isConfigValue(schemaField)) {
+            // result = result.specificNode();
+            bytecodeBlock.invokeVirtual(SPECIFIC_NODE_MTD);
+        }
 
         // return result;
-        viewMtd.getBody().ret(schemaFieldType);
+        bytecodeBlock.ret(schemaFieldType);
+
+        if (getPolymorphicTypeIdFieldFun != null) {
+            assert isPolymorphicConfigInstance(schemaField.getDeclaringClass()) : schemaField;
+
+            // tmpVar = this.typeId; OR this.field.typeId.
+            BytecodeExpression getPolymorphicTypeIdFieldValue = getPolymorphicTypeIdFieldFun.apply(viewMtd);
+            String polymorphicInstanceId = polymorphicInstanceId(schemaField.getDeclaringClass());
+
+            // if (!"first".equals(tmpVar)) throw Ex;
+            // else return value;
+            viewMtd.getBody().append(
+                new IfStatement()
+                    .condition(not(constantString(polymorphicInstanceId).invoke(STRING_EQUALS_MTD, getPolymorphicTypeIdFieldValue)))
+                    .ifTrue(throwException(ConfigurationWrongPolymorphicTypeIdException.class, getPolymorphicTypeIdFieldValue))
+                    .ifFalse(bytecodeBlock)
+            );
+        }
+        else
+            viewMtd.getBody().append(bytecodeBlock);
     }
 
     /**
      * Implements changer method from {@code CHANGE} interface.
      *
      * @param classDef Node class definition.
-     * @param schemaField Configuration Schema class field.
-     * @param fieldDef Field definition.
-     * @param nodeClassName Class name for the Node class.
+     * @param schemaField Configuration schema class field.
      * @return Definition of change method.
      */
-    private static MethodDefinition addNodeChangeMethod(
+    private MethodDefinition addNodeChangeMethod(
         ClassDefinition classDef,
         Field schemaField,
-        FieldDefinition fieldDef,
-        String nodeClassName
+        Function<MethodDefinition, BytecodeExpression> getFieldCodeFun,
+        BiFunction<MethodDefinition, BytecodeExpression, BytecodeExpression> setFieldCodeFun,
+        @Nullable Function<MethodDefinition, BytecodeExpression> getPolymorphicTypeIdFieldFun
     ) {
         Class<?> schemaFieldType = schemaField.getType();
 
         MethodDefinition changeMtd = classDef.declareMethod(
             of(PUBLIC),
-            "change" + capitalize(schemaField.getName()),
-            typeFromJavaClassName(nodeClassName),
+            changeMethodName(schemaField.getName()),
+            classDef.getType(),
             // Change argument type is a Consumer for all inner or named fields.
             arg("change", isValue(schemaField) ? type(schemaFieldType) : type(Consumer.class))
         );
 
-        BytecodeBlock changeBody = changeMtd.getBody();
+        // var change;
+        BytecodeExpression changeVar = changeMtd.getScope().getVariable("change");
 
-        // newValue = change;
-        BytecodeExpression newValue = changeMtd.getScope().getVariable("change");
+        BytecodeBlock bytecodeBlock = new BytecodeBlock();
 
         if (!schemaFieldType.isPrimitive()) {
             // Objects.requireNonNull(newValue, "change");
-            changeBody.append(invokeStatic(REQUIRE_NON_NULL, newValue, constantString("change")));
+            bytecodeBlock.append(invokeStatic(REQUIRE_NON_NULL, changeVar, constantString("change")));
         }
 
         if (isValue(schemaField)) {
-            // newValue = Box.valueOf(newValue); // Boxing.
-            if (schemaFieldType.isPrimitive())
-                newValue = invokeStatic(fieldDef.getType(), "valueOf", fieldDef.getType(), singleton(newValue));
+            BytecodeExpression newValue;
+
+            if (schemaFieldType.isPrimitive()) {
+                ParameterizedType type = type(box(schemaFieldType));
 
-            // newValue = newValue.clone();
-            if (schemaFieldType.isArray())
-                newValue = newValue.invoke("clone", Object.class).cast(schemaFieldType);
+                // newValue = Box.valueOf(newValue); // Boxing.
+                newValue = invokeStatic(type, "valueOf", type, singleton(changeVar));
+            } else if (schemaFieldType.isArray()) {
+                // newValue = newValue.clone();
+                newValue = changeVar.invoke("clone", Object.class).cast(schemaFieldType);
+            } else
+                newValue = changeVar;
 
             // this.field = newValue;
-            changeBody.append(changeMtd.getThis().setField(fieldDef, newValue));
+            bytecodeBlock.append(setFieldCodeFun.apply(changeMtd, newValue));
         }
         else {
-            // this.field = (this.field == null) ? new ValueNode() : (ValueNode)this.field.copy();
-            changeBody.append(copyNodeField(changeMtd, fieldDef));
-
-            // change.accept(this.field);
-            changeBody.append(changeMtd.getScope().getVariable("change").invoke(
-                ACCEPT,
-                changeMtd.getThis().getField(fieldDef)
-            ));
+            BytecodeExpression newValue;
+
+            if (isConfigValue(schemaField)) {
+                // newValue = (this.field == null) ? new ValueNode() : (ValueNode)this.field.copy();
+                newValue = newOrCopyNodeField(schemaField, getFieldCodeFun.apply(changeMtd));
+            }
+            else {
+                assert isNamedConfigValue(schemaField) : schemaField;
+
+                // newValue = (ValueNode)this.field.copy();
+                newValue = copyNodeField(schemaField, getFieldCodeFun.apply(changeMtd));
+            }
+
+            // this.field = newValue;
+            bytecodeBlock.append(setFieldCodeFun.apply(changeMtd, newValue));
+
+            // this.field;
+            BytecodeExpression getFieldCode = getFieldCodeFun.apply(changeMtd);
+
+            if (isPolymorphicConfig(schemaFieldType) && isConfigValue(schemaField)) {
+                // this.field.specificNode();
+                getFieldCode = getFieldCode.invoke(SPECIFIC_NODE_MTD);
+            }
+
+            // change.accept(this.field); OR change.accept(this.field.specificNode());
+            bytecodeBlock.append(changeVar.invoke(ACCEPT, getFieldCode));
         }
 
         // return this;
-        changeBody.append(changeMtd.getThis()).retObject();
+        bytecodeBlock.append(changeMtd.getThis()).retObject();
+
+        if (getPolymorphicTypeIdFieldFun != null) {
+            assert isPolymorphicConfigInstance(schemaField.getDeclaringClass()) : schemaField;
+
+            // tmpVar = this.typeId; OR this.field.typeId.
+            BytecodeExpression getPolymorphicTypeIdFieldValue = getPolymorphicTypeIdFieldFun.apply(changeMtd);
+            String polymorphicInstanceId = polymorphicInstanceId(schemaField.getDeclaringClass());
+
+            // if (!"first".equals(tmpVar)) throw Ex;
+            // else change_value;
+            changeMtd.getBody().append(
+                new IfStatement()
+                    .condition(not(constantString(polymorphicInstanceId).invoke(STRING_EQUALS_MTD, getPolymorphicTypeIdFieldValue)))
+                    .ifTrue(throwException(ConfigurationWrongPolymorphicTypeIdException.class, getPolymorphicTypeIdFieldValue))
+                    .ifFalse(bytecodeBlock)
+            );
+        }
+        else
+            changeMtd.getBody().append(bytecodeBlock);
 
         return changeMtd;
     }
@@ -642,9 +959,9 @@ public class ConfigurationAsmGenerator {
     /**
      * Implements changer bridge method from {@code CHANGE} interface.
      *
-     * @param classDef Node class definition.
+     * @param classDef        Node class definition.
      * @param changeClassName Class name for the CHANGE class.
-     * @param changeMtd Definition of change method.
+     * @param changeMtd       Definition of change method.
      */
     private static void addNodeChangeBridgeMethod(
         ClassDefinition classDef,
@@ -670,16 +987,22 @@ public class ConfigurationAsmGenerator {
     /**
      * Implements {@link InnerNode#traverseChildren(ConfigurationVisitor, boolean)} method.
      *
-     * @param classDef Class definition.
-     * @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.
+     * @param classDef                     Class definition.
+     * @param schemaClass                  Configuration schema class.
+     * @param fieldDefs                    Definitions for all fields in {@code schemaFields}.
+     * @param schemaFields                 Fields of the schema class.
+     * @param internalFields               Fields of internal extensions of the configuration schema.
+     * @param polymorphicFieldsByExtension Fields of polymorphic configuration instances grouped by them.
+     * @param polymorphicTypeIdFieldDef    Identification field for the polymorphic configuration instance.
      */
     private static void addNodeTraverseChildrenMethod(
         ClassDefinition classDef,
+        Class<?> schemaClass,
         Map<String, FieldDefinition> fieldDefs,
-        Field[] schemaFields,
-        Set<Field> extensionsFields
+        List<Field> schemaFields,
+        Collection<Field> internalFields,
+        Map<Class<?>, List<Field>> polymorphicFieldsByExtension,
+        @Nullable FieldDefinition polymorphicTypeIdFieldDef
     ) {
         MethodDefinition traverseChildrenMtd = classDef.declareMethod(
             of(PUBLIC),
@@ -691,18 +1014,57 @@ public class ConfigurationAsmGenerator {
 
         BytecodeBlock mtdBody = traverseChildrenMtd.getBody();
 
-        invokeVisitForTraverseChildren(asList(schemaFields), fieldDefs, traverseChildrenMtd)
-            .forEach(mtdBody::append);
-
-        if (!extensionsFields.isEmpty()) {
-            Variable includeInternalVar = traverseChildrenMtd.getScope().getVariable("includeInternal");
+        // invokeVisit for public (common in case polymorphic config) fields.
+        for (Field schemaField : schemaFields) {
+            mtdBody.append(
+                invokeVisit(traverseChildrenMtd, schemaField, fieldDefs.get(schemaField.getName())).pop()
+            );
+        }
 
+        if (!internalFields.isEmpty()) {
             BytecodeBlock includeInternalBlock = new BytecodeBlock();
 
-            invokeVisitForTraverseChildren(extensionsFields, fieldDefs, traverseChildrenMtd)
-                .forEach(includeInternalBlock::append);
+            for (Field internalField : internalFields) {
+                includeInternalBlock.append(
+                    invokeVisit(traverseChildrenMtd, internalField, fieldDefs.get(internalField.getName())).pop()
+                );
+            }
+
+            // if (includeInternal) invokeVisit for internal fields.
+            mtdBody.append(
+                new IfStatement()
+                    .condition(traverseChildrenMtd.getScope().getVariable("includeInternal"))
+                    .ifTrue(includeInternalBlock)
+            );
+        }
+        else if (!polymorphicFieldsByExtension.isEmpty()) {
+            assert polymorphicTypeIdFieldDef != null : schemaClass.getName();
+            assert isPolymorphicId(schemaFields.get(0)) : schemaClass.getName();
+
+            // Create switch by polymorphicTypeIdField.
+            StringSwitchBuilder switchBuilderTypeId = typeIdSwitchBuilder(traverseChildrenMtd, polymorphicTypeIdFieldDef);
 
-            mtdBody.append(new IfStatement().condition(includeInternalVar).ifTrue(includeInternalBlock));
+            for (Map.Entry<Class<?>, List<Field>> e : polymorphicFieldsByExtension.entrySet()) {
+                BytecodeBlock codeBlock = new BytecodeBlock();
+
+                for (Field polymorphicField : e.getValue()) {
+                    String fieldName = fieldName(polymorphicField);
+
+                    // invokeVisit for specific polymorphic config fields.
+                    codeBlock.append(
+                        invokeVisit(traverseChildrenMtd, polymorphicField, fieldDefs.get(fieldName)).pop()
+                    );
+                }
+
+                switchBuilderTypeId.addCase(polymorphicInstanceId(e.getKey()), codeBlock);
+            }
+
+            // if (polymorphicTypeIdField != null) switch_by_polymorphicTypeIdField
+            mtdBody.append(
+                new IfStatement()
+                    .condition(isNotNull(getThisFieldCode(traverseChildrenMtd, polymorphicTypeIdFieldDef)))
+                    .ifTrue(switchBuilderTypeId.build())
+            );
         }
 
         mtdBody.ret();
@@ -711,16 +1073,20 @@ public class ConfigurationAsmGenerator {
     /**
      * Implements {@link InnerNode#traverseChild(String, ConfigurationVisitor, boolean)} method.
      *
-     * @param classDef Class definition.
-     * @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.
+     * @param classDef                     Class definition.
+     * @param fieldDefs                    Definitions for all fields in {@code schemaFields}.
+     * @param schemaFields                 Fields of the schema class.
+     * @param internalFields               Fields of internal extensions of the configuration schema.
+     * @param polymorphicFieldsByExtension Fields of polymorphic configuration instances grouped by them.
+     * @param polymorphicTypeIdFieldDef    Identification field for the polymorphic configuration instance.
      */
     private static void addNodeTraverseChildMethod(
         ClassDefinition classDef,
         Map<String, FieldDefinition> fieldDefs,
-        Field[] schemaFields,
-        Set<Field> extensionsFields
+        Collection<Field> schemaFields,
+        Collection<Field> internalFields,
+        Map<Class<?>, List<Field>> polymorphicFieldsByExtension,
+        @Nullable FieldDefinition polymorphicTypeIdFieldDef
     ) {
         MethodDefinition traverseChildMtd = classDef.declareMethod(
             of(PUBLIC),
@@ -731,35 +1097,96 @@ public class ConfigurationAsmGenerator {
             arg("includeInternal", type(boolean.class))
         ).addException(NoSuchElementException.class);
 
-        BytecodeBlock mtdBody = traverseChildMtd.getBody();
+        Variable keyVar = traverseChildMtd.getScope().getVariable("key");
 
-        if (extensionsFields.isEmpty())
-            mtdBody.append(invokeVisitForTraverseChild(asList(schemaFields), fieldDefs, traverseChildMtd));
-        else {
-            Variable includeInternalVar = traverseChildMtd.getScope().getVariable("includeInternal");
+        // Create switch for public (common in case polymorphic config) fields only.
+        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(traverseChildMtd.getScope()).expression(keyVar);
 
-            mtdBody.append(
+        for (Field schemaField : schemaFields) {
+            String fieldName = fieldName(schemaField);
+
+            switchBuilder.addCase(
+                fieldName,
+                invokeVisit(traverseChildMtd, schemaField, fieldDefs.get(fieldName)).retObject()
+            );
+        }
+
+        if (!internalFields.isEmpty()) {
+            // Create switch for public + internal fields.
+            StringSwitchBuilder switchBuilderAllFields = new StringSwitchBuilder(traverseChildMtd.getScope())
+                .expression(keyVar)
+                .defaultCase(throwException(NoSuchElementException.class, keyVar));
+
+            for (Field schemaField : union(schemaFields, internalFields)) {
+                String fieldName = fieldName(schemaField);
+
+                switchBuilderAllFields.addCase(
+                    fieldName,
+                    invokeVisit(traverseChildMtd, schemaField, fieldDefs.get(fieldName)).retObject()
+                );
+            }
+
+            // if (includeInternal) switch_by_all_fields
+            // else switch_only_public_fields
+            traverseChildMtd.getBody().append(
                 new IfStatement()
-                    .condition(includeInternalVar)
-                    .ifTrue(invokeVisitForTraverseChild(union(extensionsFields, schemaFields), fieldDefs, traverseChildMtd))
-                    .ifFalse(invokeVisitForTraverseChild(asList(schemaFields), fieldDefs, traverseChildMtd))
+                    .condition(traverseChildMtd.getScope().getVariable("includeInternal"))
+                    .ifTrue(switchBuilderAllFields.build())
+                    .ifFalse(switchBuilder.defaultCase(throwException(NoSuchElementException.class, keyVar)).build())
             );
         }
+        else if (!polymorphicFieldsByExtension.isEmpty()) {
+            assert polymorphicTypeIdFieldDef != null : classDef.getName();
+
+            // Create switch by polymorphicTypeIdField.
+            StringSwitchBuilder switchBuilderTypeId = typeIdSwitchBuilder(traverseChildMtd, polymorphicTypeIdFieldDef);
+
+            for (Map.Entry<Class<?>, List<Field>> e : polymorphicFieldsByExtension.entrySet()) {
+                // Create switch for specific polymorphic instance.
+                StringSwitchBuilder switchBuilderPolymorphicExtension = new StringSwitchBuilder(traverseChildMtd.getScope())
+                    .expression(keyVar)
+                    .defaultCase(throwException(NoSuchElementException.class, keyVar));
+
+                for (Field polymorphicField : e.getValue()) {
+                    String fieldName = fieldName(polymorphicField);
+
+                    switchBuilderPolymorphicExtension.addCase(
+                        polymorphicField.getName(),
+                        invokeVisit(traverseChildMtd, polymorphicField, fieldDefs.get(fieldName)).retObject()
+                    );
+                }
+
+                switchBuilderTypeId.addCase(polymorphicInstanceId(e.getKey()), switchBuilderPolymorphicExtension.build());
+            }
+
+            // switch_by_common_fields
+            // switch_by_polymorphicTypeIdField
+            //      switch_by_polymorphic_0_fields
+            //      switch_by_polymorphic_1_fields
+            //      ...
+            traverseChildMtd.getBody()
+                .append(switchBuilder.defaultCase(new BytecodeBlock()).build())
+                .append(switchBuilderTypeId.build());
+        }
+        else {
+            traverseChildMtd.getBody()
+                .append(switchBuilder.defaultCase(throwException(NoSuchElementException.class, keyVar)).build());
+        }
     }
 
     /**
      * Creates bytecode block that invokes one of {@link ConfigurationVisitor}'s methods.
      *
-     * @param mtd Method definition, either {@link InnerNode#traverseChildren(ConfigurationVisitor, boolean)} or
-     *      {@link InnerNode#traverseChild(String, ConfigurationVisitor, boolean)} 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.
+     * @param fieldDef    Field definition from current class.
      * @return Bytecode block that invokes "visit*" method.
      */
     private static BytecodeBlock invokeVisit(MethodDefinition mtd, Field schemaField, FieldDefinition fieldDef) {
         Method visitMethod;
 
-        if (isValue(schemaField))
+        if (isValue(schemaField) || isPolymorphicId(schemaField))
             visitMethod = VISIT_LEAF;
         else if (isConfigValue(schemaField))
             visitMethod = VISIT_INNER;
@@ -768,7 +1195,7 @@ public class ConfigurationAsmGenerator {
 
         return new BytecodeBlock().append(mtd.getScope().getVariable("visitor").invoke(
             visitMethod,
-            constantString(fieldDef.getName()),
+            constantString(schemaField.getName()),
             mtd.getThis().getField(fieldDef)
         ));
     }
@@ -776,16 +1203,22 @@ public class ConfigurationAsmGenerator {
     /**
      * Implements {@link ConstructableTreeNode#construct(String, ConfigurationSource, boolean)} method.
      *
-     * @param classDef Class definition.
-     * @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.
+     * @param classDef                     Class definition.
+     * @param fieldDefs                    Definitions for all fields in {@code schemaFields}.
+     * @param schemaFields                 Fields of the schema class.
+     * @param internalFields               Fields of internal extensions of the configuration schema.
+     * @param polymorphicFieldsByExtension Fields of polymorphic configuration instances grouped by them.
+     * @param polymorphicTypeIdFieldDef    Identification field for the polymorphic configuration instance.
+     * @param changePolymorphicTypeIdMtd   Method for changing the type of polymorphic configuration.
      */
     private void addNodeConstructMethod(
         ClassDefinition classDef,
         Map<String, FieldDefinition> fieldDefs,
-        Field[] schemaFields,
-        Set<Field> extensionsFields
+        Collection<Field> schemaFields,
+        Collection<Field> internalFields,
+        Map<Class<?>, List<Field>> polymorphicFieldsByExtension,
+        @Nullable FieldDefinition polymorphicTypeIdFieldDef,
+        @Nullable MethodDefinition changePolymorphicTypeIdMtd
     ) {
         MethodDefinition constructMtd = classDef.declareMethod(
             of(PUBLIC),
@@ -796,38 +1229,126 @@ public class ConfigurationAsmGenerator {
             arg("includeInternal", type(boolean.class))
         ).addException(NoSuchElementException.class);
 
-        BytecodeBlock mtdBody = constructMtd.getBody();
+        Variable keyVar = constructMtd.getScope().getVariable("key");
+        Variable srcVar = constructMtd.getScope().getVariable("src");
 
-        if (extensionsFields.isEmpty())
-            mtdBody.append(treatSourceForConstruct(asList(schemaFields), fieldDefs, constructMtd)).ret();
-        else {
-            Variable includeInternalVar = constructMtd.getScope().getVariable("includeInternal");
+        // Create switch for public (common in case polymorphic config) fields only.
+        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(constructMtd.getScope()).expression(keyVar);
 
-            mtdBody.append(
-                new IfStatement()
-                    .condition(includeInternalVar)
-                    .ifTrue(treatSourceForConstruct(union(extensionsFields, schemaFields), fieldDefs, constructMtd))
-                    .ifFalse(treatSourceForConstruct(asList(schemaFields), fieldDefs, constructMtd))
+        for (Field schemaField : schemaFields) {
+            String fieldName = fieldName(schemaField);
+            FieldDefinition fieldDef = fieldDefs.get(fieldName);
+
+            if (isPolymorphicId(schemaField)) {
+                // src == null ? null : src.unwrap(FieldType.class);
+                BytecodeExpression getTypeIdFromSrcVar = inlineIf(
+                    isNull(srcVar),
+                    constantNull(fieldDef.getType()),
+                    srcVar.invoke(UNWRAP, constantClass(fieldDef.getType())).cast(fieldDef.getType())
+                );
+
+                // this.changePolymorphicTypeId(src == null ? null : src.unwrap(FieldType.class));
+                switchBuilder.addCase(
+                    fieldName,
+                    new BytecodeBlock()
+                        .append(constructMtd.getThis())
+                        .append(getTypeIdFromSrcVar)
+                        .invokeVirtual(changePolymorphicTypeIdMtd)
+                        .ret()
+                );
+            }
+            else {
+                switchBuilder.addCase(
+                    fieldName,
+                    treatSourceForConstruct(constructMtd, schemaField, fieldDef).ret()
+                );
+            }
+        }
+
+        if (!internalFields.isEmpty()) {
+            // Create switch for public + internal fields.
+            StringSwitchBuilder switchBuilderAllFields = new StringSwitchBuilder(constructMtd.getScope())
+                .expression(keyVar)
+                .defaultCase(throwException(NoSuchElementException.class, keyVar));
+
+            for (Field schemaField : union(schemaFields, internalFields)) {
+                String fieldName = fieldName(schemaField);
+
+                switchBuilderAllFields.addCase(
+                    fieldName,
+                    treatSourceForConstruct(constructMtd, schemaField, fieldDefs.get(fieldName)).ret()
+                );
+            }
+
+            // if (includeInternal) switch_by_all_fields
+            // else switch_only_public_fields
+            constructMtd.getBody().append(
+                new IfStatement().condition(constructMtd.getScope().getVariable("includeInternal"))
+                    .ifTrue(switchBuilderAllFields.build())
+                    .ifFalse(switchBuilder.defaultCase(throwException(NoSuchElementException.class, keyVar)).build())
             ).ret();
         }
+        else if (!polymorphicFieldsByExtension.isEmpty()) {
+            assert polymorphicTypeIdFieldDef != null : classDef.getName();
+
+            // Create switch by polymorphicTypeIdField.
+            StringSwitchBuilder switchBuilderTypeId = typeIdSwitchBuilder(constructMtd, polymorphicTypeIdFieldDef);
+
+            for (Map.Entry<Class<?>, List<Field>> e : polymorphicFieldsByExtension.entrySet()) {
+                // Create switch for specific polymorphic instance.
+                StringSwitchBuilder switchBuilderPolymorphicExtension = new StringSwitchBuilder(constructMtd.getScope())
+                    .expression(keyVar)
+                    .defaultCase(throwException(NoSuchElementException.class, keyVar));
+
+                for (Field polymorphicField : e.getValue()) {
+                    String fieldName = fieldName(polymorphicField);
+                    FieldDefinition fieldDef = fieldDefs.get(fieldName);
+
+                    switchBuilderPolymorphicExtension.addCase(
+                        polymorphicField.getName(),
+                        treatSourceForConstruct(constructMtd, polymorphicField, fieldDef).ret()
+                    );
+                }
+
+                switchBuilderTypeId.addCase(polymorphicInstanceId(e.getKey()), switchBuilderPolymorphicExtension.build());
+            }
+
+            // switch_by_common_fields
+            // switch_by_polymorphicTypeIdField
+            //      switch_by_polymorphic_0_fields
+            //      switch_by_polymorphic_1_fields
+            //      ...
+            constructMtd.getBody()
+                .append(switchBuilder.defaultCase(new BytecodeBlock()).build())
+                .append(switchBuilderTypeId.build())
+                .ret();
+        }
+        else {
+            constructMtd.getBody()
+                .append(switchBuilder.defaultCase(throwException(NoSuchElementException.class, keyVar)).build())
+                .ret();
+        }
     }
 
     /**
      * Implements {@link InnerNode#constructDefault(String)} method.
      *
-     * @param classDef Node class definition.
-     * @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.
+     * @param classDef                     Class definition.
+     * @param specFields                   Field definitions for the schema and its extensions: {@code _spec#}.
+     * @param fieldDefs                    Definitions for all fields in {@code schemaFields}.
+     * @param schemaFields                 Fields of the schema class.
+     * @param internalFields               Fields of internal extensions of the configuration schema.
+     * @param polymorphicFieldsByExtension Fields of polymorphic configuration instances grouped by them.
+     * @param polymorphicTypeIdFieldDef    Identification field for the polymorphic configuration instance.
      */
     private static void addNodeConstructDefaultMethod(
         ClassDefinition classDef,
         Map<Class<?>, FieldDefinition> specFields,
         Map<String, FieldDefinition> fieldDefs,
-        Field[] schemaFields,
-        Set<Field> extensionsFields
+        Collection<Field> schemaFields,
+        Collection<Field> internalFields,
+        Map<Class<?>, List<Field>> polymorphicFieldsByExtension,
+        @Nullable FieldDefinition polymorphicTypeIdFieldDef
     ) {
         MethodDefinition constructDfltMtd = classDef.declareMethod(
             of(PUBLIC),
@@ -838,73 +1359,122 @@ public class ConfigurationAsmGenerator {
 
         Variable keyVar = constructDfltMtd.getScope().getVariable("key");
 
-        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(constructDfltMtd.getScope())
-            .expression(keyVar);
-
-        for (Field schemaField : concat(asList(schemaFields), extensionsFields)) {
-            if (!isValue(schemaField))
-                continue;
-
-            if (!schemaField.getAnnotation(Value.class).hasDefault()) {
-                switchBuilder.addCase(schemaField.getName(), new BytecodeBlock());
-
-                continue;
+        // Create switch for public (common in case polymorphic config) + internal fields.
+        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(constructDfltMtd.getScope()).expression(keyVar);
+
+        for (Field schemaField : concat(schemaFields, internalFields)) {
+            if (isValue(schemaField) || isPolymorphicId(schemaField)) {
+                String fieldName = schemaField.getName();
+
+                if (isValue(schemaField) && !hasDefault(schemaField) ||
+                    isPolymorphicId(schemaField) && !schemaField.getAnnotation(PolymorphicId.class).hasDefault()) {
+                    // return;
+                    switchBuilder.addCase(fieldName, new BytecodeBlock().ret());
+                } else {
+                    FieldDefinition fieldDef = fieldDefs.get(fieldName);
+                    FieldDefinition specFieldDef = specFields.get(schemaField.getDeclaringClass());
+
+                    // this.field = spec_#.field;
+                    switchBuilder.addCase(
+                        fieldName,
+                        addNodeConstructDefault(constructDfltMtd, schemaField, fieldDef, specFieldDef).ret()
+                    );
+                }
             }
+        }
 
-            FieldDefinition fieldDef = fieldDefs.get(schemaField.getName());
-
-            Class<?> schemaFieldType = schemaField.getType();
-
-            // defaultValue = _spec#.field;
-            FieldDefinition specField = specFields.get(schemaField.getDeclaringClass());
-            BytecodeExpression defaultValue = constructDfltMtd.getThis().getField(specField).getField(schemaField);
+        if (!polymorphicFieldsByExtension.isEmpty()) {
+            // Create switch by polymorphicTypeIdField.
+            StringSwitchBuilder switchBuilderTypeId = typeIdSwitchBuilder(constructDfltMtd, polymorphicTypeIdFieldDef);
+
+            for (Map.Entry<Class<?>, List<Field>> e : polymorphicFieldsByExtension.entrySet()) {
+                // Create switch for specific polymorphic instance.
+                StringSwitchBuilder switchBuilderPolymorphicExtension = new StringSwitchBuilder(constructDfltMtd.getScope())
+                    .expression(keyVar)
+                    .defaultCase(throwException(NoSuchElementException.class, keyVar));
+
+                for (Field polymorphicField : e.getValue()) {
+                    if (isValue(polymorphicField)) {
+                        String fieldName = fieldName(polymorphicField);
+
+                        if (!hasDefault(polymorphicField)) {
+                            // return;
+                            switchBuilder.addCase(fieldName, new BytecodeBlock().ret());
+                        }
+                        else {
+                            FieldDefinition fieldDef = fieldDefs.get(fieldName);
+                            FieldDefinition specFieldDef = specFields.get(polymorphicField.getDeclaringClass());
+
+                            // this.field = spec_#.field;
+                            switchBuilderPolymorphicExtension.addCase(
+                                polymorphicField.getName(),
+                                addNodeConstructDefault(constructDfltMtd, polymorphicField, fieldDef, specFieldDef).ret()
+                            );
+                        }
+                    }
+                }
 
-            // defaultValue = Box.valueOf(defaultValue); // Boxing.
-            if (schemaFieldType.isPrimitive()) {
-                defaultValue = invokeStatic(
-                    fieldDef.getType(),
-                    "valueOf",
-                    fieldDef.getType(),
-                    singleton(defaultValue)
+                switchBuilderTypeId.addCase(
+                    polymorphicInstanceId(e.getKey()),
+                    switchBuilderPolymorphicExtension.build()
                 );
             }
 
-            // defaultValue = defaultValue.clone();
-            if (schemaFieldType.isArray())
-                defaultValue = defaultValue.invoke("clone", Object.class).cast(schemaFieldType);
-
-            // this.field = defaultValue;
-            BytecodeBlock caseClause = new BytecodeBlock()
-                .append(constructDfltMtd.getThis().setField(fieldDef, defaultValue));
-
-            switchBuilder.addCase(schemaField.getName(), caseClause);
+            // switch_by_common_fields
+            // switch_by_polymorphicTypeIdField
+            //      switch_by_polymorphic_0_fields
+            //      switch_by_polymorphic_1_fields
+            //      ...
+            constructDfltMtd.getBody()
+                .append(switchBuilder.defaultCase(new BytecodeBlock()).build())
+                .append(switchBuilderTypeId.build())
+                .ret();
+        }
+        else {
+            constructDfltMtd.getBody()
+                .append(switchBuilder.defaultCase(throwException(NoSuchElementException.class, keyVar)).build())
+                .ret();
         }
-
-        // Default option is to throw "NoSuchElementException(key)".
-        switchBuilder.defaultCase(new BytecodeBlock()
-            .append(newInstance(NoSuchElementException.class, keyVar))
-            .throwObject()
-        );
-
-        constructDfltMtd.getBody().append(switchBuilder.build()).ret();
     }
 
     /**
      * Copies field into itself or instantiates it if the field is null.
-     * @param mtd Method definition.
-     * @param fieldDef Field definition.
+     * Code like: {@code this.field == null ? new ValueNode() : (ValueNode)this.field.copy();}.
+     *
+     * @param schemaField Configuration schema class field.
+     * @param getFieldCode Bytecode of getting the field, for example: {@code this.field} or {@code this.field.field};
      * @return Bytecode expression.
      */
-    @NotNull private static BytecodeExpression copyNodeField(MethodDefinition mtd, FieldDefinition fieldDef) {
-        return mtd.getThis().setField(fieldDef, inlineIf(
-            isNull(mtd.getThis().getField(fieldDef)),
-            newInstance(fieldDef.getType()),
-            mtd.getThis().getField(fieldDef).invoke(COPY).cast(fieldDef.getType())
-        ));
+    private BytecodeExpression newOrCopyNodeField(Field schemaField, BytecodeExpression getFieldCode) {
+        ParameterizedType nodeType = typeFromJavaClassName(schemasInfo.get(schemaField.getType()).nodeClassName);
+
+        // this.field == null ? new ValueNode() : (ValueNode)this.field.copy();
+        return inlineIf(
+            isNull(getFieldCode),
+            newInstance(nodeType),
+            copyNodeField(schemaField, getFieldCode)
+        );
     }
 
     /**
-     * Creates {@code *Node::new} lambda expression with {@link Supplier} type.
+     * Copies field into itself.
+     * Code like: {@code (ValueNode)this.field.copy();}.
+     *
+     * @param schemaField Configuration schema class field.
+     * @param getFieldCode Bytecode of getting the field, for example: {@code this.field} or {@code this.field.field};
+     * @return Bytecode expression.
+     */
+    private BytecodeExpression copyNodeField(Field schemaField, BytecodeExpression getFieldCode) {
+        ParameterizedType nodeType = isNamedConfigValue(schemaField)
+            ? type(NamedListNode.class) : typeFromJavaClassName(schemasInfo.get(schemaField.getType()).nodeClassName);
+
+        // (ValueNode)this.field.copy();
+        return getFieldCode.invoke(COPY).cast(nodeType);
+    }
+
+    /**
+     * Creates {@code *Node::new} lambda expression with {@link Supplier} type.
+     *
      * @param nodeClassName Name of the {@code *Node} class.
      * @return InvokeDynamic bytecode expression.
      */
@@ -930,17 +1500,20 @@ public class ConfigurationAsmGenerator {
     /**
      * 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.
+     * @param schemaClass        Configuration schema class.
+     * @param internalExtensions Internal extensions of the configuration schema.
+     * @param schemaFields       Fields of the schema class.
+     * @param internalFields     Fields of internal extensions of the configuration schema.
+     * @param polymorphicFields  Fields of polymorphic 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
+        Set<Class<?>> internalExtensions,
+        Set<Class<?>> polymorphicExtensions,
+        Collection<Field> schemaFields,
+        Collection<Field> internalFields,
+        Collection<Field> polymorphicFields
     ) {
         SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
 
@@ -951,24 +1524,64 @@ public class ConfigurationAsmGenerator {
             of(PUBLIC, FINAL),
             internalName(schemaClassInfo.cfgImplClassName),
             type(superClass),
-            configClassInterfaces(schemaClass, schemaExtensions)
+            configClassInterfaces(schemaClass, internalExtensions)
         );
 
         // Fields.
         Map<String, FieldDefinition> fieldDefs = new HashMap<>();
 
-        for (Field schemaField : concat(asList(schemaFields), extensionsFields))
-            fieldDefs.put(schemaField.getName(), addConfigurationImplField(classDef, schemaField));
+        // To store the id of the polymorphic configuration instance.
+        FieldDefinition polymorphicTypeIdFieldDef = null;
+
+        for (Field schemaField : concat(schemaFields, internalFields, polymorphicFields)) {
+            String fieldName = fieldName(schemaField);
+
+            FieldDefinition fieldDef = addConfigurationImplField(classDef, schemaField, fieldName);
+
+            fieldDefs.put(fieldName, fieldDef);
+
+            if (isPolymorphicId(schemaField))
+                polymorphicTypeIdFieldDef = fieldDef;
+        }
 
         // Constructor
-        addConfigurationImplConstructor(classDef, schemaClassInfo, fieldDefs, schemaFields, extensionsFields);
+        addConfigurationImplConstructor(
+            classDef,
+            schemaClass,
+            fieldDefs,
+            schemaFields,
+            internalFields,
+            polymorphicFields
+        );
 
-        for (Field schemaField : concat(asList(schemaFields), extensionsFields))
-            addConfigurationImplGetMethod(classDef, schemaClass, fieldDefs, schemaField);
+        for (Field schemaField : concat(schemaFields, internalFields))
+            addConfigurationImplGetMethod(classDef, schemaField, fieldDefs.get(fieldName(schemaField)));
 
         // org.apache.ignite.internal.configuration.DynamicConfiguration#configType
         addCfgImplConfigTypeMethod(classDef, typeFromJavaClassName(schemaClassInfo.cfgClassName));
 
+        if (!polymorphicExtensions.isEmpty()) {
+            addCfgSpecificConfigTreeMethod(classDef, schemaClass, polymorphicExtensions, polymorphicTypeIdFieldDef);
+
+            addCfgRemoveMembersMethod(
+                classDef,
+                schemaClass,
+                polymorphicExtensions,
+                fieldDefs,
+                polymorphicFields,
+                polymorphicTypeIdFieldDef
+            );
+
+            addCfgAddMembersMethod(
+                classDef,
+                schemaClass,
+                polymorphicExtensions,
+                fieldDefs,
+                polymorphicFields,
+                polymorphicTypeIdFieldDef
+            );
+        }
+
         return classDef;
     }
 
@@ -977,49 +1590,61 @@ public class ConfigurationAsmGenerator {
      * <ul>
      *     <li>
      *         {@code @Value public type fieldName}<br/>becomes<br/>
-     *         {@code private DynamicProperty fieldName}
+     *         {@code public DynamicProperty fieldName}
      *     </li>
      *     <li>
      *         {@code @ConfigValue public MyConfigurationSchema fieldName}<br/>becomes<br/>
-     *         {@code private MyConfiguration fieldName}
+     *         {@code public MyConfiguration fieldName}
      *     </li>
      *     <li>
      *         {@code @NamedConfigValue public type fieldName}<br/>becomes<br/>
-     *         {@code private NamedListConfiguration fieldName}
+     *         {@code public NamedListConfiguration fieldName}
+     *     </li>
+     *     <li>
+     *         {@code @PolymorphicId public String fieldName}<br/>becomes<br/>
+     *         {@code public String fieldName}
      *     </li>
      * </ul>
-     * @param classDef Configuration impl class definition.
+     *
+     * @param classDef    Configuration impl class definition.
      * @param schemaField Configuration Schema class field.
+     * @param fieldName Field name, if {@code null} will be used {@link Field#getName}.
      * @return Declared field definition.
      */
-    private FieldDefinition addConfigurationImplField(ClassDefinition classDef, Field schemaField) {
+    private FieldDefinition addConfigurationImplField(
+        ClassDefinition classDef,
+        Field schemaField,
+        String fieldName
+    ) {
         ParameterizedType fieldType;
 
         if (isConfigValue(schemaField))
-            fieldType = typeFromJavaClassName(schemasInfo.get(schemaField.getType()).cfgClassName);
+            fieldType = typeFromJavaClassName(schemasInfo.get(schemaField.getType()).cfgImplClassName);
         else if (isNamedConfigValue(schemaField))
             fieldType = type(NamedListConfiguration.class);
         else
             fieldType = type(DynamicProperty.class);
 
-        return classDef.declareField(of(PRIVATE), schemaField.getName(), fieldType);
+        return classDef.declareField(of(PUBLIC), fieldName, fieldType);
     }
 
     /**
      * Implements default constructor for the configuration class. It initializes all fields and adds them to members
      * collection.
-     * @param classDef Configuration impl class definition.
-     * @param schemaClassInfo Configuration Schema class info.
-     * @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.
+     *
+     * @param classDef       Configuration impl class definition.
+     * @param schemaClass    Configuration schema class.
+     * @param fieldDefs      Field definitions for all fields of configuration impl class.
+     * @param schemaFields   Fields of the schema class.
+     * @param internalFields Fields of internal extensions of the configuration schema.
      */
     private void addConfigurationImplConstructor(
         ClassDefinition classDef,
-        SchemaClassesInfo schemaClassInfo,
+        Class<?> schemaClass,
         Map<String, FieldDefinition> fieldDefs,
-        Field[] schemaFields,
-        Set<Field> extensionsFields
+        Collection<Field> schemaFields,
+        Collection<Field> internalFields,
+        Collection<Field> polymorphicFields
     ) {
         MethodDefinition ctor = classDef.declareConstructor(
             of(PUBLIC),
@@ -1034,8 +1659,9 @@ public class ConfigurationAsmGenerator {
         Variable changerVar = ctor.getScope().getVariable("changer");
         Variable listenOnlyVar = ctor.getScope().getVariable("listenOnly");
 
-        Constructor<?> superCtor = schemaClassInfo.direct ?
-            DIRECT_DYNAMIC_CONFIGURATION_CTOR : DYNAMIC_CONFIGURATION_CTOR;
+        SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
+
+        Constructor<?> superCtor = schemaClassInfo.direct ? DIRECT_DYNAMIC_CONFIGURATION_CTOR : DYNAMIC_CONFIGURATION_CTOR;
 
         BytecodeBlock ctorBody = ctor.getBody()
             .append(ctor.getThis())
@@ -1049,12 +1675,12 @@ public class ConfigurationAsmGenerator {
         BytecodeExpression thisKeysVar = ctor.getThis().getField("keys", List.class);
 
         int newIdx = 0;
-        for (Field schemaField : concat(asList(schemaFields), extensionsFields)) {
-            FieldDefinition fieldDef = fieldDefs.get(schemaField.getName());
+        for (Field schemaField : concat(schemaFields, internalFields, polymorphicFields)) {
+            String fieldName = schemaField.getName();
 
             BytecodeExpression newValue;
 
-            if (isValue(schemaField)) {
+            if (isValue(schemaField) || isPolymorphicId(schemaField)) {
                 Class<?> fieldImplClass = schemaField.isAnnotationPresent(DirectAccess.class) ?
                     DirectDynamicProperty.class : DynamicProperty.class;
 
@@ -1062,10 +1688,11 @@ public class ConfigurationAsmGenerator {
                 newValue = newInstance(
                     fieldImplClass,
                     thisKeysVar,
-                    constantString(schemaField.getName()),
+                    constantString(fieldName),
                     rootKeyVar,
                     changerVar,
-                    listenOnlyVar
+                    listenOnlyVar,
+                    constantBoolean(isPolymorphicId(schemaField))
                 );
             }
             else {
@@ -1078,7 +1705,7 @@ public class ConfigurationAsmGenerator {
                     newValue = newInstance(
                         cfgImplParameterizedType,
                         thisKeysVar,
-                        constantString(schemaField.getName()),
+                        constantString(fieldName),
                         rootKeyVar,
                         changerVar,
                         listenOnlyVar
@@ -1108,7 +1735,7 @@ public class ConfigurationAsmGenerator {
                     newValue = newInstance(
                         fieldImplClass,
                         thisKeysVar,
-                        constantString(schemaField.getName()),
+                        constantString(fieldName),
                         rootKeyVar,
                         changerVar,
                         listenOnlyVar,
@@ -1158,11 +1785,15 @@ public class ConfigurationAsmGenerator {
                 }
             }
 
+            FieldDefinition fieldDef = fieldDefs.get(fieldName(schemaField));
+
             // this.field = newValue;
             ctorBody.append(ctor.getThis().setField(fieldDef, newValue));
 
-            // add(this.field);
-            ctorBody.append(ctor.getThis().invoke(DYNAMIC_CONFIGURATION_ADD, ctor.getThis().getField(fieldDef)));
+            if (!isPolymorphicConfigInstance(schemaField.getDeclaringClass())) {
+                // add(this.field);
+                ctorBody.append(ctor.getThis().invoke(DYNAMIC_CONFIGURATION_ADD_MTD, ctor.getThis().getField(fieldDef)));
+            }
         }
 
         ctorBody.ret();
@@ -1170,50 +1801,58 @@ public class ConfigurationAsmGenerator {
 
     /**
      * Implements accessor method in configuration impl class.
-     * @param classDef Configuration impl class definition.
-     * @param schemaClass Configuration Schema class.
-     * @param fieldDefs Field definitions for all fields of configuration impl class.
+     *
+     * @param classDef    Configuration impl class definition.
      * @param schemaField Configuration Schema class field.
+     * @param fieldDefs Field definitions.
      */
     private void addConfigurationImplGetMethod(
         ClassDefinition classDef,
-        Class<?> schemaClass,
-        Map<String, FieldDefinition> fieldDefs,
-        Field schemaField
+        Field schemaField,
+        FieldDefinition... fieldDefs
     ) {
+        assert !nullOrEmpty(fieldDefs);
+
         Class<?> schemaFieldType = schemaField.getType();
 
         String fieldName = schemaField.getName();
-        FieldDefinition fieldDef = fieldDefs.get(fieldName);
 
         ParameterizedType returnType;
 
+        SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaFieldType);
+
         if (isConfigValue(schemaField))
-            returnType = typeFromJavaClassName(schemasInfo.get(schemaFieldType).cfgClassName);
+            returnType = typeFromJavaClassName(schemaClassInfo.cfgClassName);
         else if (isNamedConfigValue(schemaField))
             returnType = type(NamedConfigurationTree.class);
         else {
-            assert isValue(schemaField) : schemaClass;
+            assert isValue(schemaField) || isPolymorphicId(schemaField) : schemaField;
 
             returnType = type(ConfigurationValue.class);
         }
 
+        // public ConfigurationProperty fieldName()
         MethodDefinition viewMtd = classDef.declareMethod(
             of(PUBLIC),
             fieldName,
             returnType
         );
 
-        BytecodeBlock viewBody = viewMtd.getBody();
+        // result = this.field;
+        BytecodeBlock body = viewMtd.getBody().append(getThisFieldCode(viewMtd, fieldDefs));
 
-        viewBody
-            .append(viewMtd.getThis())
-            .getField(fieldDef)
-            .retObject();
+        if (isPolymorphicConfig(schemaFieldType) && isConfigValue(schemaField)) {
+            // result = this.field.specificConfigTree();
+            body.invokeVirtual(SPECIFIC_CONFIG_TREE_MTD);
+        }
+
+        // return result;
+        body.retObject();
     }
 
     /**
      * Replaces first letter in string with its upper-cased variant.
+     *
      * @param name Some string.
      * @return Capitalized version of passed string.
      */
@@ -1223,6 +1862,7 @@ public class ConfigurationAsmGenerator {
 
     /**
      * Returns internalized version of class name, replacing dots with slashes.
+     *
      * @param className Class name (with package).
      * @return Internal class name.
      * @see Type#getInternalName(Class)
@@ -1232,8 +1872,8 @@ public class ConfigurationAsmGenerator {
     }
 
     /**
-     * Creates boxed version of the class. Types that it can box: {@code boolean}, {@code int}, {@code long} and
-     *      {@code double}. Other primitive types are not supported by configuration framework.
+     * Creates boxed version of the class.
+     *
      * @param clazz Maybe primitive class.
      * @return Not primitive class that represents parameter class.
      */
@@ -1244,196 +1884,931 @@ public class ConfigurationAsmGenerator {
     }
 
     /**
-     * Create bytecode blocks that invokes of {@link ConfigurationVisitor}'s methods for
-     * {@link InnerNode#traverseChildren(ConfigurationVisitor, boolean)}.
+     * Get interfaces for {@link InnerNode} definition for a configuration schema.
      *
-     * @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.
+     * @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) {
+        Collection<ParameterizedType> res = new ArrayList<>();
+
+        for (Class<?> cls : concat(List.of(schemaClass), schemaExtensions)) {
+            res.add(typeFromJavaClassName(viewClassName(cls)));
+            res.add(typeFromJavaClassName(changeClassName(cls)));
+        }
+
+        return res.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 ParameterizedType[] configClassInterfaces(Class<?> schemaClass, Set<Class<?>> schemaExtensions) {
+        List<ParameterizedType> result = Stream.concat(Stream.of(schemaClass), schemaExtensions.stream())
+            .map(cls -> typeFromJavaClassName(configurationClassName(cls)))
+            .collect(toCollection(ArrayList::new));
+
+        if (schemasInfo.get(schemaClass).direct)
+            result.add(type(DirectConfigurationProperty.class));
+
+        return result.toArray(new ParameterizedType[0]);
+    }
+
+    /**
+     * Add {@link DynamicConfiguration#configType} method implementation to the class. It looks like the following code:
+     * <pre><code>
+     * public Class configType() {
+     *     return RootConfiguration.class;
+     * }
+     * </code></pre>
+     * @param classDef Class definition.
+     * @param clazz Definition of the configuration interface, for example {@code RootConfiguration}.
+     */
+    private static void addCfgImplConfigTypeMethod(ClassDefinition classDef, ParameterizedType clazz) {
+        classDef.declareMethod(of(PUBLIC), "configType", type(Class.class))
+            .getBody()
+            .append(constantClass(clazz))
+            .retObject();
+    }
+
+    /**
+     * Create a {@code *Node} for the polymorphic configuration instance schema.
+     *
+     * @param schemaClass Polymorphic configuration schema (parent).
+     * @param polymorphicExtension Polymorphic configuration instance schema (child).
+     * @param schemaInnerNodeClassDef {@link InnerNode} definition for the polymorphic configuration schema {@code schemaClass}.
+     * @param schemaFields Schema fields of polymorphic configuration {@code schemaClass}.
+     * @param polymorphicFields Schema fields of a polymorphic configuration instance {@code polymorphicExtension}.
+     */
+    private ClassDefinition createPolymorphicExtensionNodeClass(
+        Class<?> schemaClass,
+        Class<?> polymorphicExtension,
+        ClassDefinition schemaInnerNodeClassDef,
+        Collection<Field> schemaFields,
+        Collection<Field> polymorphicFields
+    ) {
+        SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
+        SchemaClassesInfo polymorphicExtensionClassInfo = schemasInfo.get(polymorphicExtension);
+
+        // Node class definition.
+        ClassDefinition classDef = new ClassDefinition(
+            of(PUBLIC, FINAL),
+            internalName(polymorphicExtensionClassInfo.nodeClassName),
+            type(Object.class),
+            nodeClassInterfaces(polymorphicExtension, Set.of())
+        );
+
+        // private final ParentNode this$0;
+        FieldDefinition parentInnerNodeFieldDef = classDef.declareField(
+            of(PRIVATE, FINAL),
+            "this$0",
+            typeFromJavaClassName(schemaClassInfo.nodeClassName)
+        );
+
+        // Constructor.
+        MethodDefinition constructorMtd = classDef.declareConstructor(
+            of(PUBLIC),
+            arg("delegate", typeFromJavaClassName(schemaClassInfo.nodeClassName))
+        );
+
+        Variable delegateVar = constructorMtd.getScope().getVariable("delegate");
+
+        // Constructor body.
+        constructorMtd.getBody()
+            .append(constructorMtd.getThis())
+            .invokeConstructor(Object.class)
+            .append(constructorMtd.getThis().setField(
+                parentInnerNodeFieldDef,
+                delegateVar
+            ))
+            .ret();
+
+        Map<String, FieldDefinition> fieldDefs = schemaInnerNodeClassDef.getFields().stream()
+            .collect(toMap(FieldDefinition::getName, identity()));
+
+        // Creates view and change methods for parent schema.
+        for (Field schemaField : schemaFields) {
+            FieldDefinition schemaFieldDef = fieldDefs.get(fieldName(schemaField));
+
+            addNodeViewMethod(
+                classDef,
+                schemaField,
+                viewMtd -> getThisFieldCode(viewMtd, parentInnerNodeFieldDef, schemaFieldDef),
+                null
+            );
+
+            // Read only.
+            if (isPolymorphicId(schemaField))
+                continue;
+
+            MethodDefinition changeMtd0 = addNodeChangeMethod(
+                classDef,
+                schemaField,
+                changeMtd -> getThisFieldCode(changeMtd, parentInnerNodeFieldDef, schemaFieldDef),
+                (changeMtd, newValue) -> setThisFieldCode(changeMtd, newValue, parentInnerNodeFieldDef, schemaFieldDef),
+                null
+            );
+
+            addNodeChangeBridgeMethod(classDef, schemaClassInfo.changeClassName, changeMtd0);
+        }
+
+        FieldDefinition polymorphicTypeIdFieldDef = fieldDefs.get(polymorphicIdField(schemaClass).getName());
+
+        // Creates view and change methods for specific polymorphic instance schema.
+        for (Field polymorphicField : polymorphicFields) {
+            FieldDefinition polymorphicFieldDef = fieldDefs.get(fieldName(polymorphicField));
+
+            addNodeViewMethod(
+                classDef,
+                polymorphicField,
+                viewMtd -> getThisFieldCode(viewMtd, parentInnerNodeFieldDef, polymorphicFieldDef),
+                viewMtd -> getThisFieldCode(viewMtd, parentInnerNodeFieldDef, polymorphicTypeIdFieldDef)
+            );
+
+            MethodDefinition changeMtd0 = addNodeChangeMethod(
+                classDef,
+                polymorphicField,
+                changeMtd -> getThisFieldCode(changeMtd, parentInnerNodeFieldDef, polymorphicFieldDef),
+                (changeMtd, newValue) -> setThisFieldCode(changeMtd, newValue, parentInnerNodeFieldDef, polymorphicFieldDef),
+                changeMtd -> getThisFieldCode(changeMtd, parentInnerNodeFieldDef, polymorphicTypeIdFieldDef)
+            );
+
+            addNodeChangeBridgeMethod(classDef, polymorphicExtensionClassInfo.changeClassName, changeMtd0);
+        }
+
+        ParameterizedType returnType = typeFromJavaClassName(schemaClassInfo.changeClassName);
+
+        // Creates Node#convert.
+        MethodDefinition convertMtd = classDef.declareMethod(
+            of(PUBLIC),
+            CONVERT_MTD_NAME,
+            returnType,
+            arg("changeClass", Class.class)
+        );
+
+        // return this.this$0.convert(changeClass);
+        convertMtd.getBody()
+            .append(getThisFieldCode(convertMtd, parentInnerNodeFieldDef))
+            .append(convertMtd.getScope().getVariable("changeClass"))
+            .invokeVirtual(schemaInnerNodeClassDef.getType(), CONVERT_MTD_NAME, returnType, type(Class.class))
+            .retObject();
+
+        return classDef;
+    }
+
+    /**
+     * Create a {@code *CfgImpl} for the polymorphic configuration instance schema.
+     *
+     * @param schemaClass Polymorphic configuration schema (parent).
+     * @param polymorphicExtension Polymorphic configuration instance schema (child).
+     * @param schemaCfgImplClassDef {@link DynamicConfiguration} definition for the polymorphic configuration schema {@code schemaClass}.
+     * @param schemaFields Schema fields of polymorphic configuration {@code schemaClass}.
+     * @param polymorphicFields Schema fields of a polymorphic configuration instance {@code polymorphicExtension}.
      */
-    private static Collection<BytecodeNode> invokeVisitForTraverseChildren(
+    private ClassDefinition createPolymorphicExtensionCfgImplClass(
+        Class<?> schemaClass,
+        Class<?> polymorphicExtension,
+        ClassDefinition schemaCfgImplClassDef,
         Collection<Field> schemaFields,
+        Collection<Field> polymorphicFields
+    ) {
+        SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
+        SchemaClassesInfo polymorphicExtensionClassInfo = schemasInfo.get(polymorphicExtension);
+
+        Class<?> superClass = schemaClassInfo.direct || polymorphicExtensionClassInfo.direct
+            ? DirectConfigurationTreeWrapper.class : ConfigurationTreeWrapper.class;
+
+        // Configuration impl class definition.
+        ClassDefinition classDef = new ClassDefinition(
+            of(PUBLIC, FINAL),
+            internalName(polymorphicExtensionClassInfo.cfgImplClassName),
+            type(superClass),
+            configClassInterfaces(polymorphicExtension, Set.of())
+        );
+
+        // private final ParentCfgImpl this$0;
+        FieldDefinition parentCfgImplFieldDef = classDef.declareField(
+            of(PRIVATE, FINAL),
+            "this$0",
+            typeFromJavaClassName(schemaClassInfo.cfgImplClassName)
+        );
+
+        // Constructor.
+        MethodDefinition constructorMtd = classDef.declareConstructor(
+            of(PUBLIC),
+            arg("delegate", typeFromJavaClassName(schemaClassInfo.cfgImplClassName))
+        );
+
+        Variable delegateVar = constructorMtd.getScope().getVariable("delegate");
+
+        // Constructor body.
+        // super(parent);
+        // this.this$0 = parent;
+        constructorMtd.getBody()
+            .append(constructorMtd.getThis())
+            .append(delegateVar)
+            .invokeConstructor(superClass, ConfigurationTree.class)
+            .append(constructorMtd.getThis().setField(
+                parentCfgImplFieldDef,
+                delegateVar
+            ))
+            .ret();
+
+        Map<String, FieldDefinition> fieldDefs = schemaCfgImplClassDef.getFields().stream()
+            .collect(toMap(FieldDefinition::getName, identity()));
+
+        for (Field schemaField : concat(schemaFields, polymorphicFields)) {
+            addConfigurationImplGetMethod(
+                classDef,
+                schemaField,
+                parentCfgImplFieldDef,
+                fieldDefs.get(fieldName(schemaField))
+            );
+        }
+
+        return classDef;
+    }
+
+    /**
+     * Adds a {@link InnerNode#specificNode} override for the polymorphic configuration case.
+     *
+     * @param classDef                  Definition of a polymorphic configuration class (parent).
+     * @param polymorphicExtensions     Polymorphic configuration instance schemas (children).
+     * @param polymorphicTypeIdFieldDef Identification field for the polymorphic configuration instance.
+     */
+    private void addNodeSpecificNodeMethod(
+        ClassDefinition classDef,
+        Set<Class<?>> polymorphicExtensions,
+        FieldDefinition polymorphicTypeIdFieldDef
+    ) {
+        MethodDefinition specificNodeMtd = classDef.declareMethod(
+            of(PUBLIC),
+            SPECIFIC_NODE_MTD.getName(),
+            type(Object.class)
+        );
+
+        StringSwitchBuilder switchBuilder = typeIdSwitchBuilder(specificNodeMtd, polymorphicTypeIdFieldDef);
+
+        for (Class<?> polymorphicExtension : polymorphicExtensions) {
+            SchemaClassesInfo polymorphicExtensionClassInfo = schemasInfo.get(polymorphicExtension);
+
+            switchBuilder.addCase(
+                polymorphicInstanceId(polymorphicExtension),
+                newInstance(
+                    typeFromJavaClassName(polymorphicExtensionClassInfo.nodeClassName),
+                    specificNodeMtd.getThis()
+                ).ret()
+            );
+        }
+
+        specificNodeMtd.getBody().append(switchBuilder.build());
+    }
+
+    /**
+     * Adds a {@code *Node#convert} for the polymorphic configuration case.
+     *
+     * @param classDef                   Definition of a polymorphic configuration class {@code schemaClass}.
+     * @param schemaClass                Polymorphic configuration schema (parent).
+     * @param polymorphicExtensions      Polymorphic configuration instance schemas (children).
+     * @param changePolymorphicTypeIdMtd Method for changing the type of polymorphic configuration.
+     */
+    private void addNodeConvertMethod(
+        ClassDefinition classDef,
+        Class<?> schemaClass,
+        Set<Class<?>> polymorphicExtensions,
+        MethodDefinition changePolymorphicTypeIdMtd
+    ) {
+        SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
+
+        MethodDefinition convertMtd = classDef.declareMethod(
+            of(PUBLIC),
+            CONVERT_MTD_NAME,
+            typeFromJavaClassName(schemaClassInfo.changeClassName),
+            arg("changeClass", Class.class)
+        );
+
+        // changeClass.getName();
+        BytecodeExpression changeClassName = convertMtd.getScope()
+            .getVariable("changeClass")
+            .invoke(CLASS_GET_NAME_MTD);
+
+        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(convertMtd.getScope())
+            .expression(changeClassName)
+            .defaultCase(throwException(ConfigurationWrongPolymorphicTypeIdException.class, changeClassName));
+
+        for (Class<?> polymorphicExtension : polymorphicExtensions) {
+            SchemaClassesInfo polymorphicExtensionClassInfo = schemasInfo.get(polymorphicExtension);
+
+            // this.changePolymorphicTypeId(polymorphicTypeId);
+            // return new ChildNode(this);
+            switchBuilder.addCase(
+                polymorphicExtensionClassInfo.changeClassName,
+                new BytecodeBlock()
+                    .append(constantString(polymorphicInstanceId(polymorphicExtension)))
+                    .invokeVirtual(changePolymorphicTypeIdMtd)
+                    .append(newInstance(
+                        typeFromJavaClassName(polymorphicExtensionClassInfo.nodeClassName),
+                        convertMtd.getThis()
+                    ))
+                    .retObject()
+            );
+        }
+
+        convertMtd.getBody()
+            .append(convertMtd.getThis())
+            .append(switchBuilder.build())
+            .ret();
+    }
+
+    /**
+     * Adds a {@code Node#changeTypeId} for the polymorphic configuration case.
+     *
+     * @param classDef                  Definition of a polymorphic configuration class (parent).
+     * @param fieldDefs                 Definitions for all fields in {@code classDef}.
+     * @param polymorphicExtensions     Polymorphic configuration instance schemas (children).
+     * @param polymorphicFields         Fields of polymorphic extensions.
+     * @param polymorphicTypeIdFieldDef Identification field for the polymorphic configuration instance.
+     * @return Method definition.
+     */
+    private MethodDefinition addNodeChangePolymorphicTypeIdMethod(
+        ClassDefinition classDef,
         Map<String, FieldDefinition> fieldDefs,
-        MethodDefinition traverseChildrenMtd
+        Set<Class<?>> polymorphicExtensions,
+        Collection<Field> polymorphicFields,
+        FieldDefinition polymorphicTypeIdFieldDef
     ) {
-        if (schemaFields.isEmpty())
-            return List.of();
-        else {
-            return schemaFields.stream()
-                .map(field -> invokeVisit(traverseChildrenMtd, field, fieldDefs.get(field.getName())).pop())
+        MethodDefinition changePolymorphicTypeIdMtd = classDef.declareMethod(
+            of(PUBLIC),
+            changeMethodName(polymorphicTypeIdFieldDef.getName()),
+            type(void.class),
+            arg("typeId", String.class)
+        );
+
+        Variable typeIdVar = changePolymorphicTypeIdMtd.getScope().getVariable("typeId");
+
+        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(changePolymorphicTypeIdMtd.getScope())
+            .expression(typeIdVar)
+            .defaultCase(throwException(ConfigurationWrongPolymorphicTypeIdException.class, typeIdVar));
+
+        for (Class<?> polymorphicExtension : polymorphicExtensions) {
+            // Fields that need to be cleared when changing the type of the polymorphic configuration instance.
+            Collection<Field> resetFields = polymorphicFields.stream()
+                .filter(f -> !polymorphicExtension.equals(f.getDeclaringClass()))
                 .collect(toList());
+
+            // this.typeId = typeId;
+            BytecodeBlock codeBlock = new BytecodeBlock()
+                .append(setThisFieldCode(changePolymorphicTypeIdMtd, typeIdVar, polymorphicTypeIdFieldDef));
+
+            // Reset fields.
+            for (Field resetField : resetFields) {
+                FieldDefinition fieldDef = fieldDefs.get(fieldName(resetField));
+
+                if (isValue(resetField) || isConfigValue(resetField)) {
+                    // this.field = null;
+                    codeBlock.append(setThisFieldCode(
+                        changePolymorphicTypeIdMtd,
+                        constantNull(fieldDef.getType()),
+                        fieldDef
+                    ));
+                }
+                else {
+                    // this.field = new NamedListNode<>(key, ValueNode::new, "polymorphicIdFieldName");
+                    codeBlock.append(setThisFieldCode(changePolymorphicTypeIdMtd, newNamedListNode(resetField), fieldDef));
+                }
+            }
+
+            // ConfigurationUtil.addDefaults(this);
+            codeBlock
+                .append(changePolymorphicTypeIdMtd.getThis())
+                .invokeStatic(ADD_DEFAULTS_MTD);
+
+            switchBuilder.addCase(polymorphicInstanceId(polymorphicExtension), codeBlock);
         }
+
+        // if(typeId.equals(this.typeId)) return;
+        // else switch(typeId)...
+        changePolymorphicTypeIdMtd.getBody()
+            .append(typeIdVar)
+            .append(getThisFieldCode(changePolymorphicTypeIdMtd, polymorphicTypeIdFieldDef))
+            .append(
+                new IfStatement()
+                    .condition(new BytecodeBlock().invokeVirtual(STRING_EQUALS_MTD))
+                    .ifTrue(new BytecodeBlock().ret())
+                    .ifFalse(switchBuilder.build().ret())
+            );
+
+        return changePolymorphicTypeIdMtd;
     }
 
     /**
-     * Created switch bytecode block that invokes of {@link ConfigurationVisitor}'s methods for
-     *     {@link InnerNode#traverseChild(String, ConfigurationVisitor, boolean)}.
+     * Adds a {@link DynamicConfiguration#specificConfigTree} override for the polymorphic configuration case.
      *
-     * @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.
+     * @param classDef                  Definition of a polymorphic configuration class (parent).
+     * @param schemaClass               Polymorphic configuration schema (parent).
+     * @param polymorphicExtensions     Polymorphic configuration instance schemas (children).
+     * @param polymorphicTypeIdFieldDef Identification field for the polymorphic configuration instance.
      */
-    private static BytecodeNode invokeVisitForTraverseChild(
-        Collection<Field> schemaFields,
+    private void addCfgSpecificConfigTreeMethod(
+        ClassDefinition classDef,
+        Class<?> schemaClass,
+        Set<Class<?>> polymorphicExtensions,
+        FieldDefinition polymorphicTypeIdFieldDef
+    ) {
+        MethodDefinition specificConfigMtd = classDef.declareMethod(
+            of(PUBLIC),
+            SPECIFIC_CONFIG_TREE_MTD.getName(),
+            type(ConfigurationTree.class)
+        );
+
+        // String tmpStr;
+        Variable tmpStrVar = specificConfigMtd.getScope().createTempVariable(String.class);
+
+        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(specificConfigMtd.getScope())
+            .expression(tmpStrVar)
+            .defaultCase(throwException(ConfigurationWrongPolymorphicTypeIdException.class, tmpStrVar));
+
+        for (Class<?> polymorphicExtension : polymorphicExtensions) {
+            // return new SpecialCfgImpl(this);
+            switchBuilder.addCase(
+                polymorphicInstanceId(polymorphicExtension),
+                newInstance(
+                    typeFromJavaClassName(schemasInfo.get(polymorphicExtension).cfgImplClassName),
+                    specificConfigMtd.getThis()
+                ).ret()
+            );
+        }
+
+        // ConfigNode
+        ParameterizedType nodeType = typeFromJavaClassName(schemasInfo.get(schemaClass).nodeClassName);
+
+        // Object tmpObj;
+        Variable tmpObjVar = specificConfigMtd.getScope().createTempVariable(Object.class);
+
+        // tmpObj = this.refreshValue();
+        // tmpStr = ((ConfigNode) tmpObj).typeId;
+        // switch(tmpStr) ...
+        specificConfigMtd.getBody()
+            .append(tmpObjVar.set(specificConfigMtd.getThis().invoke(REFRESH_VALUE_MTD)))
+            .append(tmpStrVar.set(tmpObjVar.cast(nodeType).getField(polymorphicTypeIdFieldDef.getName(), String.class)))
+            .append(switchBuilder.build())
+            .ret();
+    }
+
+    /**
+     * Adds a {@code DynamicConfiguration#removeMembers} override for the polymorphic configuration case.
+     *
+     * @param classDef                  Definition of a polymorphic configuration class (parent).
+     * @param schemaClass               Polymorphic configuration schema (parent).
+     * @param polymorphicExtensions     Polymorphic configuration instance schemas (children).
+     * @param fieldDefs                 Field definitions for all fields of {@code classDef}.
+     * @param polymorphicFields         Fields of polymorphic extensions.
+     * @param polymorphicTypeIdFieldDef Identification field for the polymorphic configuration instance.
+     */
+    private void addCfgRemoveMembersMethod(
+        ClassDefinition classDef,
+        Class<?> schemaClass,
+        Set<Class<?>> polymorphicExtensions,
         Map<String, FieldDefinition> fieldDefs,
-        MethodDefinition traverseChildMtd
+        Collection<Field> polymorphicFields,
+        FieldDefinition polymorphicTypeIdFieldDef
     ) {
-        Variable keyVar = traverseChildMtd.getScope().getVariable("key");
+        MethodDefinition removeMembersMtd = classDef.declareMethod(
+            of(PUBLIC),
+            "removeMembers",
+            type(void.class),
+            arg("oldValue", type(Object.class)),
+            arg("members", type(Map.class))
+        );
 
-        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(traverseChildMtd.getScope()).expression(keyVar);
+        // InnerNode oldValue;
+        Variable oldValueVar = removeMembersMtd.getScope().getVariable("oldValue");
 
-        for (Field schemaField : schemaFields) {
-            String fieldName = schemaField.getName();
+        // Map members;
+        Variable membersVar = removeMembersMtd.getScope().getVariable("members");
 
-            FieldDefinition fieldDef = fieldDefs.get(fieldName);
+        // String tmpStr;
+        Variable tmpStrVar = removeMembersMtd.getScope().createTempVariable(String.class);
+
+        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(removeMembersMtd.getScope())
+            .expression(tmpStrVar)
+            .defaultCase(throwException(ConfigurationWrongPolymorphicTypeIdException.class, tmpStrVar));
+
+        for (Class<?> polymorphicExtension : polymorphicExtensions) {
+            Collection<Field> removeFields = polymorphicFields.stream()
+                .filter(f -> !polymorphicExtension.equals(f.getDeclaringClass()))
+                .collect(toList());
+
+            BytecodeBlock blockCode = new BytecodeBlock();
+
+            for (Field removeField : removeFields) {
+                // this.removeMember(members, this.field);
+                blockCode
+                    .append(removeMembersMtd.getThis())
+                    .append(membersVar)
+                    .append(getThisFieldCode(removeMembersMtd, fieldDefs.get(fieldName(removeField))))
+                    .invokeVirtual(REMOVE_MEMBER_MTD);
+            }
 
-            // Visit result should be immediately returned.
-            switchBuilder.addCase(fieldName, invokeVisit(traverseChildMtd, schemaField, fieldDef).retObject());
+            switchBuilder.addCase(polymorphicInstanceId(polymorphicExtension), blockCode);
         }
 
-        // Default option is to throw "NoSuchElementException(key)".
-        switchBuilder.defaultCase(new BytecodeBlock()
-            .append(newInstance(NoSuchElementException.class, keyVar))
-            .throwObject()
+        // ConfigNode
+        ParameterizedType nodeType = typeFromJavaClassName(schemasInfo.get(schemaClass).nodeClassName);
+
+        // tmpStr = ((ConfigNode) oldValue).typeId;
+        // switch(tmpStr) ...
+        removeMembersMtd.getBody()
+            .append(tmpStrVar.set(oldValueVar.cast(nodeType).getField(polymorphicTypeIdFieldDef.getName(), String.class)))
+            .append(switchBuilder.build())
+            .ret();
+    }
+
+    /**
+     * Adds a {@code DynamicConfiguration#addMembers} override for the polymorphic configuration case.
+     *
+     * @param classDef                  Definition of a polymorphic configuration class (parent).
+     * @param schemaClass               Polymorphic configuration schema (parent).
+     * @param polymorphicExtensions     Polymorphic configuration instance schemas (children).
+     * @param fieldDefs                 Field definitions for all fields of {@code classDef}.
+     * @param polymorphicFields         Fields of polymorphic extensions.
+     * @param polymorphicTypeIdFieldDef Identification field for the polymorphic configuration instance.
+     */
+    private void addCfgAddMembersMethod(
+        ClassDefinition classDef,
+        Class<?> schemaClass,
+        Set<Class<?>> polymorphicExtensions,
+        Map<String, FieldDefinition> fieldDefs,
+        Collection<Field> polymorphicFields,
+        FieldDefinition polymorphicTypeIdFieldDef
+    ) {
+        MethodDefinition removeMembersMtd = classDef.declareMethod(
+            of(PUBLIC),
+            "addMembers",
+            type(void.class),
+            arg("newValue", type(Object.class)),
+            arg("members", type(Map.class))
         );
 
-        return switchBuilder.build();
+        // InnerNode newValue;
+        Variable newValueVar = removeMembersMtd.getScope().getVariable("newValue");
+
+        // Map members;
+        Variable membersVar = removeMembersMtd.getScope().getVariable("members");
+
+        // String tmpStr;
+        Variable tmpStrVar = removeMembersMtd.getScope().createTempVariable(String.class);
+
+        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(removeMembersMtd.getScope())
+            .expression(tmpStrVar)
+            .defaultCase(throwException(ConfigurationWrongPolymorphicTypeIdException.class, tmpStrVar));
+
+        for (Class<?> polymorphicExtension : polymorphicExtensions) {
+            Collection<Field> addFields = polymorphicFields.stream()
+                .filter(f -> polymorphicExtension.equals(f.getDeclaringClass()))
+                .collect(toList());
+
+            BytecodeBlock blockCode = new BytecodeBlock();
+
+            for (Field addField : addFields) {
+                // this.addMember(members, this.field);
+                blockCode
+                    .append(removeMembersMtd.getThis())
+                    .append(membersVar)
+                    .append(getThisFieldCode(removeMembersMtd, fieldDefs.get(fieldName(addField))))
+                    .invokeVirtual(ADD_MEMBER_MTD);
+            }
+
+            switchBuilder.addCase(polymorphicInstanceId(polymorphicExtension), blockCode);
+        }
+
+        // ConfigNode
+        ParameterizedType nodeType = typeFromJavaClassName(schemasInfo.get(schemaClass).nodeClassName);
+
+        // tmpStr = ((ConfigNode) newValue).typeId;
+        // switch(tmpStr) ...
+        removeMembersMtd.getBody()
+            .append(tmpStrVar.set(newValueVar.cast(nodeType).getField(polymorphicTypeIdFieldDef.getName(), String.class)))
+            .append(switchBuilder.build())
+            .ret();
     }
 
     /**
-     * Created switch bytecode block that invokes of construct methods for
-     *     {@link InnerNode#construct(String, ConfigurationSource, boolean)}.
+     * Creates 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.
+     * @param schemaField Schema field.
+     * @param schemaFieldDef Schema field definition.
+     * @return Bytecode block that invokes of construct method for field.
      */
-    private BytecodeNode treatSourceForConstruct(
-        Collection<Field> schemaFields,
-        Map<String, FieldDefinition> fieldDefs,
-        MethodDefinition constructMtd
+    private BytecodeBlock treatSourceForConstruct(
+        MethodDefinition constructMtd,
+        Field schemaField,
+        FieldDefinition schemaFieldDef
     ) {
-        Variable keyVar = constructMtd.getScope().getVariable("key");
+        BytecodeBlock codeBlock = new BytecodeBlock();
+
+        Variable thisVar = constructMtd.getThis();
         Variable srcVar = constructMtd.getScope().getVariable("src");
 
-        StringSwitchBuilder switchBuilder = new StringSwitchBuilder(constructMtd.getScope()).expression(keyVar);
+        if (isValue(schemaField)) {
+            // this.field = src == null ? null : src.unwrap(FieldType.class);
+            codeBlock.append(thisVar.setField(schemaFieldDef, inlineIf(
+                isNull(srcVar),
+                constantNull(schemaFieldDef.getType()),
+                srcVar.invoke(UNWRAP, constantClass(schemaFieldDef.getType())).cast(schemaFieldDef.getType())
+            )));
+        }
+        else if (isConfigValue(schemaField)) {
+            BytecodeNode setField;
 
-        for (Field schemaField : schemaFields) {
-            FieldDefinition fieldDef = fieldDefs.get(schemaField.getName());
+            ParameterizedType fieldDefType = schemaFieldDef.getType();
 
-            BytecodeBlock caseClause = new BytecodeBlock();
+            if (isPolymorphicConfig(schemaField.getType())) {
+                Field polymorphicIdField = polymorphicIdField(schemaField.getType());
 
-            switchBuilder.addCase(schemaField.getName(), caseClause);
+                assert polymorphicIdField != null : schemaField.getType().getName();
 
-            // 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;
+                BytecodeExpression thisField = getThisFieldCode(constructMtd, schemaFieldDef);
+
+                // String tmpStr;
+                Variable tmpStrVar = constructMtd.getScope().createTempVariable(String.class);
+
+                // this.field = (FieldType) this.field.copy();
+                // if(tmpStr != null) this.field.changeTypeId(tmpStr);
+                BytecodeBlock copyWithChange = new BytecodeBlock()
+                    .append(setThisFieldCode(constructMtd, thisField.invoke(COPY).cast(fieldDefType), schemaFieldDef))
+                    .append(new IfStatement()
+                        .condition(isNotNull(tmpStrVar))
+                        .ifTrue(thisField.invoke(changeMethodName(polymorphicIdField.getName()), void.class, tmpStrVar))
+                    );
+
+                // this.field = new FieldType();
+                // if(tmpStr != null) this.field.changeTypeId(tmpStr);
+                // else {
+                //      this.field.constructDefault("typeId");
+                //      if(this.field.typeId == null) throw new IllegalStateException();
+                // }
+                BytecodeBlock newInstanceWithChange = new BytecodeBlock()
+                    .append(setThisFieldCode(constructMtd, newInstance(fieldDefType), schemaFieldDef))
+                    .append(new IfStatement()
+                        .condition(isNotNull(tmpStrVar))
+                        .ifTrue(thisField.invoke(changeMethodName(polymorphicIdField.getName()), void.class, tmpStrVar))
+                        .ifFalse(new BytecodeBlock()
+                            .append(thisField.invoke(CONSTRUCT_DEFAULT_MTD, constantString(polymorphicIdField.getName())))
+                            .append(new IfStatement()
+                                .condition(isNull(thisField.getField(polymorphicIdField.getName(), String.class)))
+                                .ifTrue(throwException(
+                                    IllegalStateException.class,
+                                    constantString("Polymorphic configuration type is not defined: " +
+                                        polymorphicIdField.getDeclaringClass().getName())
+                                ))
+                            )
+                        )
+                    );
+
+                // tmpStr = src.polymorphicTypeId("typeId");
+                // if(this.field == null)
+                setField = new BytecodeBlock()
+                    .append(tmpStrVar.set(srcVar.invoke(POLYMORPHIC_TYPE_ID_MTD, constantString(polymorphicIdField.getName()))))
+                    .append(new IfStatement()
+                        .condition(isNull(thisField))
+                        .ifTrue(newInstanceWithChange)
+                        .ifFalse(copyWithChange)
+                    );
             }
-            // this.field = src == null ? new NamedListNode<>(key, ValueNode::new) : src.descend(field = field.copy()));
             else {
-                NamedConfigValue namedCfgAnnotation = schemaField.getAnnotation(NamedConfigValue.class);
+                // newValue = this.field == null ? new ValueNode() : field.copy());
+                BytecodeExpression newValue = newOrCopyNodeField(schemaField, getThisFieldCode(constructMtd, schemaFieldDef));
 
-                String fieldNodeClassName = schemasInfo.get(schemaField.getType()).nodeClassName;
+                // this.field = newValue;
+                setField = setThisFieldCode(constructMtd, newValue, schemaFieldDef);
+            }
 
-                caseClause.append(new IfStatement()
+            codeBlock.append(
+                new IfStatement()
                     .condition(isNull(srcVar))
-                    .ifTrue(constructMtd.getThis().setField(
-                        fieldDef,
-                        newInstance(
-                            NamedListNode.class,
-                            constantString(namedCfgAnnotation.syntheticKeyName()),
-                            newNamedListElementLambda(fieldNodeClassName)
-                        )
-                    ))
+                    .ifTrue(setThisFieldCode(constructMtd, constantNull(fieldDefType), schemaFieldDef))
                     .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)))
+                        .append(setField)
+                        .append(srcVar.invoke(DESCEND, thisVar.getField(schemaFieldDef)))
                     )
-                );
-            }
+            );
+        }
+        else {
+            // this.field = src == null ? new NamedListNode<>(key, ValueNode::new, "polymorphicIdFieldName")
+            // : src.descend(field = field.copy()));
+            codeBlock.append(new IfStatement()
+                .condition(isNull(srcVar))
+                .ifTrue(setThisFieldCode(constructMtd, newNamedListNode(schemaField), schemaFieldDef))
+                .ifFalse(new BytecodeBlock()
+                    .append(setThisFieldCode(
+                        constructMtd,
+                        thisVar.getField(schemaFieldDef).invoke(COPY).cast(schemaFieldDef.getType()),
+                        schemaFieldDef
+                    )).append(srcVar.invoke(DESCEND, thisVar.getField(schemaFieldDef)))
+                )
+            );
         }
 
-        // Default option is to throw "NoSuchElementException(key)".
-        switchBuilder.defaultCase(new BytecodeBlock()
-            .append(newInstance(NoSuchElementException.class, keyVar))
-            .throwObject()
-        );
+        return codeBlock;
+    }
+
+    /**
+     * Creates a bytecode block of code that sets the default value for a field from the schema for
+     *      {@link InnerNode#constructDefault(String)}.
+     *
+     * @param constructDfltMtd Method definition {@link InnerNode#constructDefault(String)}
+     *      defined in {@code *Node} class.
+     * @param schemaField Schema field.
+     * @param schemaFieldDef Schema field definition.
+     * @param specFieldDef Definition of the schema field.: {@code _spec#}.
+     * @return Bytecode block that sets the default value for a field from the schema.
+     */
+    private static BytecodeBlock addNodeConstructDefault(
+        MethodDefinition constructDfltMtd,
+        Field schemaField,
+        FieldDefinition schemaFieldDef,
+        FieldDefinition specFieldDef
+    ) {
+        Variable thisVar = constructDfltMtd.getThis();
+
+        // defaultValue = _spec#.field;
+        BytecodeExpression defaultValue = thisVar.getField(specFieldDef).getField(schemaField);
 
-        return switchBuilder.build();
+        Class<?> schemaFieldType = schemaField.getType();
+
+        // defaultValue = Box.valueOf(defaultValue); // Boxing.
+        if (schemaFieldType.isPrimitive()) {
+            defaultValue = invokeStatic(
+                schemaFieldDef.getType(),
+                "valueOf",
+                schemaFieldDef.getType(),
+                singleton(defaultValue)
+            );
+        }
+
+        // defaultValue = defaultValue.clone();
+        if (schemaFieldType.isArray())
+            defaultValue = defaultValue.invoke("clone", Object.class).cast(schemaFieldType);
+
+        // this.field = defaultValue;
+        return new BytecodeBlock().append(thisVar.setField(schemaFieldDef, defaultValue));
     }
 
     /**
-     * Get interfaces for {@link InnerNode} definition for a configuration schema.
+     * Creates the bytecode:
+     * <pre><code>
+     * throw new Exception(msg);
+     * </code></pre>
      *
-     * @param schemaClass Configuration schema class.
-     * @param schemaExtensions Internal extensions of the configuration schema.
-     * @return Interfaces for {@link InnerNode} definition for a configuration schema.
+     * @param throwableClass Exception class.
+     * @param parameters Exception constructor parameters.
+     * @return Exception throwing bytecode.
      */
-    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);
+    private static BytecodeBlock throwException(
+        Class<? extends Throwable> throwableClass,
+        BytecodeExpression... parameters
+    ) {
+        return new BytecodeBlock().append(newInstance(throwableClass, parameters)).throwObject();
     }
 
     /**
-     * Get interfaces for {@link DynamicConfiguration} definition for a configuration schema.
+     * Returns the name of the configuration schema field.
+     * If the schema contains {@link PolymorphicConfigInstance},
+     * it will return "{@link Field#getName()} + {@code "#"} + {@link PolymorphicConfigInstance#value()}"
+     * otherwise "{@link Field#getName}".
+     *
+     * @param f Configuration schema field.
+     * @return Field name.
+     */
+    private static String fieldName(Field f) {
+        return isPolymorphicConfigInstance(f.getDeclaringClass())
+            ? f.getName() + "#" + polymorphicInstanceId(f.getDeclaringClass()) : f.getName();
+    }
+
+    /**
+     * Creates a string switch builder by {@code typeIdFieldDef}.
+     *
+     * @param mtdDef Method definition.
+     * @param typeIdFieldDef Field definition that contains the id of the polymorphic configuration instance.
+     * @return String switch builder by {@code typeIdFieldDef}.
+     */
+    private static StringSwitchBuilder typeIdSwitchBuilder(MethodDefinition mtdDef, FieldDefinition typeIdFieldDef) {
+        BytecodeExpression typeIdVar = mtdDef.getThis().getField(typeIdFieldDef);
+
+        return new StringSwitchBuilder(mtdDef.getScope())
+            .expression(typeIdVar)
+            .defaultCase(throwException(ConfigurationWrongPolymorphicTypeIdException.class, typeIdVar));
+    }
+
+    /**
+     * Generates bytecode to get a class field like {@code this.field;} or {@code this.field.field;}.
+     *
+     * @param mtdDef Class method definition.
+     * @param fieldDefs Field definitions.
+     * @return Bytecode for getting the field.
+     */
+    private static BytecodeExpression getThisFieldCode(MethodDefinition mtdDef, FieldDefinition... fieldDefs) {
+        assert !nullOrEmpty(fieldDefs);
+
+        // this.field;
+        BytecodeExpression getFieldCode = mtdDef.getThis().getField(fieldDefs[0]);
+
+        // this.field.field; etc.
+        for (int i = 1; i < fieldDefs.length; i++)
+            getFieldCode = getFieldCode.getField(fieldDefs[i]);
+
+        return getFieldCode;
+    }
+
+    /**
+     * Generates bytecode to set a class field like {@code this.field = value;} or {@code this.field.field = value;}.
+     *
+     * @param mtdDef Class method definition.
+     * @param value Value of the field to be set.
+     * @param fieldDefs Field definitions.
+     * @return Bytecode for setting the field value.
+     */
+    private static BytecodeExpression setThisFieldCode(
+        MethodDefinition mtdDef,
+        BytecodeExpression value,
+        FieldDefinition... fieldDefs
+    ) {
+        assert !nullOrEmpty(fieldDefs);
+
+        if (fieldDefs.length == 1) {
+            // this.field = value;
+            return mtdDef.getThis().setField(fieldDefs[0], value);
+        }
+        else {
+            // this.field;
+            BytecodeExpression getFieldCode = mtdDef.getThis().getField(fieldDefs[0]);
+
+            // this.field.field; etc.
+            for (int i = 1; i < fieldDefs.length - 1; i++)
+                getFieldCode = getFieldCode.getField(fieldDefs[i]);
+
+            // this.field.field = value;
+            return getFieldCode.setField(fieldDefs[fieldDefs.length - 1], value);
+        }
+    }
+
+    /**
+     * Returns configuration schema field with {@link PolymorphicId}.
      *
      * @param schemaClass Configuration schema class.
-     * @param schemaExtensions Internal extensions of the configuration schema.
-     * @return Interfaces for {@link DynamicConfiguration} definition for a configuration schema.
+     * @return Configuration schema field with {@link PolymorphicId}.
      */
-    private ParameterizedType[] configClassInterfaces(Class<?> schemaClass, Set<Class<?>> schemaExtensions) {
-        var result = new ArrayList<ParameterizedType>();
+    @Nullable private static Field polymorphicIdField(Class<?> schemaClass) {
+        assert isPolymorphicConfig(schemaClass) : schemaClass.getName();
 
-        Stream.concat(Stream.of(schemaClass), schemaExtensions.stream())
-            .map(cls -> typeFromJavaClassName(configurationClassName(cls)))
-            .forEach(result::add);
+        for (Field f : schemaClass.getDeclaredFields()) {
+            if (isPolymorphicId(f))
+                return f;
+        }
 
-        if (schemasInfo.get(schemaClass).direct)
-            result.add(type(DirectConfigurationProperty.class));
+        return null;
+    }
 
-        return result.toArray(new ParameterizedType[0]);
+    /**
+     * Returns name of the method to change the field.
+     *
+     * @param schemaField Configuration schema field.
+     * @return Name of the method to change the field.
+     */
+    private static String changeMethodName(String schemaField) {
+        return "change" + capitalize(schemaField);
     }
 
     /**
-     * Add {@link DynamicConfiguration#configType} method implementation to the class. It looks like the following code:
-     * <pre><code>
-     * public Class configType() {
-     *     return RootConfiguration.class;
-     * }
-     * </code></pre>
-     * @param classDef Class definition.
-     * @param clazz Definition of the configuration interface, for example {@code RootConfiguration}.
+     * Creates bytecode like: new NamedListNode<>(key, ValueNode::new, "polymorphicIdFieldName");
+     *
+     * @param schemaField Schema field with {@link NamedConfigValue}.
+     * @return Bytecode like: new NamedListNode<>(key, ValueNode::new, "polymorphicIdFieldName");
      */
-    private void addCfgImplConfigTypeMethod(ClassDefinition classDef, ParameterizedType clazz) {
-        classDef.declareMethod(of(PUBLIC), "configType", type(Class.class))
-            .getBody()
-            .append(constantClass(clazz))
-            .retObject();
+    private BytecodeExpression newNamedListNode(Field schemaField) {
+        assert isNamedConfigValue(schemaField) : schemaField;
+
+        Class<?> fieldType = schemaField.getType();
+
+        SchemaClassesInfo fieldClassNames = schemasInfo.get(fieldType);
+
+        NamedConfigValue namedCfgAnnotation = schemaField.getAnnotation(NamedConfigValue.class);
+
+        return newInstance(
+            NamedListNode.class,
+            constantString(namedCfgAnnotation.syntheticKeyName()),
+            newNamedListElementLambda(fieldClassNames.nodeClassName),
+            isPolymorphicConfig(fieldType) ? constantString(polymorphicIdField(fieldType).getName()) : constantNull(String.class)
+        );
     }
 }
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 ae8359c..cf1584d 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
@@ -23,6 +23,7 @@ import java.util.NoSuchElementException;
 import com.typesafe.config.ConfigList;
 import com.typesafe.config.ConfigObject;
 import com.typesafe.config.ConfigValue;
+import org.apache.ignite.configuration.ConfigurationWrongPolymorphicTypeIdException;
 import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
 import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
 import org.jetbrains.annotations.Nullable;
@@ -126,6 +127,11 @@ class HoconObjectConfigurationSource implements ConfigurationSource {
                     );
                 }
             }
+            catch (ConfigurationWrongPolymorphicTypeIdException e) {
+                throw new IllegalArgumentException(
+                    "Polymorphic configuration type is not correct: " + e.getMessage()
+                );
+            }
         }
     }
 }
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConfigurationSource.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConfigurationSource.java
index 5150385..29cbc3b 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConfigurationSource.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConfigurationSource.java
@@ -17,7 +17,11 @@
 
 package org.apache.ignite.internal.configuration.tree;
 
-/** */
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Configuration source.
+ */
 public interface ConfigurationSource {
     /**
      * Treat current configuration source as a leaf value and try to convert it to the specific class.
@@ -45,4 +49,14 @@ public interface ConfigurationSource {
      */
     default void reset() {
     }
+
+    /**
+     * Returns the identifier of the polymorphic configuration instance.
+     *
+     * @param fieldName Name of the field in the configuration schema that should store the identifier of the polymorphic configuration instance.
+     * @return Identifier of the polymorphic configuration instance.
+     */
+    @Nullable default String polymorphicTypeId(String fieldName) {
+        return null;
+    }
 }
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 aa68f1e..3f13c2f 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
@@ -19,7 +19,9 @@ package org.apache.ignite.internal.configuration.tree;
 
 import java.util.NoSuchElementException;
 
-/** */
+/**
+ * Interface for filling the configuration node.
+ */
 public interface ConstructableTreeNode {
     /**
      * Initializes {@code key} element of the object with the content from the source.
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
index df63a40..d25cb2a 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
@@ -86,7 +86,7 @@ public class ConverterToMapVisitor implements ConfigurationVisitor<Object> {
         deque.push(list);
 
         for (String subkey : node.namedListKeys()) {
-            node.get(subkey).accept(subkey, this);
+            node.getInnerNode(subkey).accept(subkey, this);
 
             ((Map<String, Object>)list.get(list.size() - 1)).put(node.syntheticKeyName(), subkey);
         }
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 058112b..e4711d3 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
@@ -19,7 +19,9 @@ package org.apache.ignite.internal.configuration.tree;
 
 import java.util.NoSuchElementException;
 
-/** */
+/**
+ * Configuration node implementation.
+ */
 public abstract class InnerNode implements TraversableTreeNode, ConstructableTreeNode, Cloneable {
     /** {@inheritDoc} */
     @Override public final <T> T accept(String key, ConfigurationVisitor<T> visitor) {
@@ -173,4 +175,15 @@ public abstract class InnerNode implements TraversableTreeNode, ConstructableTre
             throw new IllegalStateException(e);
         }
     }
+
+    /**
+     * Returns specific {@code Node} of the value.
+     * Overridden for polymorphic configuration to get a specific polymorphic configuration instance.
+     *
+     * @param <NODE> Type of the {@code Node}.
+     * @return Specific {@code Node} of the value.
+     */
+    public <NODE> NODE specificNode() {
+        return (NODE)this;
+    }
 }
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 2bec423..34e330b 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
@@ -28,47 +28,55 @@ import java.util.function.Consumer;
 import java.util.function.Supplier;
 import org.apache.ignite.configuration.NamedListChange;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
 import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
+import org.apache.ignite.internal.configuration.util.ConfigurationUtil.LeafConfigurationSource;
+import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.addDefaults;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.leafNodeVisitor;
 
 /**
- * Configuration node implementation for the collection of named {@link InnerNode}s. Unlike implementations of
- * {@link InnerNode}, this class is used for every named list in configuration.
+ * Configuration node implementation for the collection of named {@link InnerNode}s.
+ * Unlike implementations of {@link InnerNode}, this class is used for every named list in configuration.
  *
- * @param <N> Type of the {@link InnerNode} that is stored in named list node object.
+ * @param <N> Type of the {@code Node} ({@link InnerNode} or instances of polymorphic configuration)
+ *      that is stored in named list node object.
  */
-public final class NamedListNode<N extends InnerNode> implements NamedListChange<N, N>, TraversableTreeNode, ConstructableTreeNode {
+public final class NamedListNode<N> implements NamedListChange<N, N>, TraversableTreeNode, ConstructableTreeNode {
     /** Name of a synthetic configuration property that describes the order of elements in a named list. */
     public static final String ORDER_IDX = "<order>";
 
-    /**
-     * Name of a synthetic configuration property that's used to store "key" of the named list element in the storage.
-     */
+    /** Name of a synthetic configuration property that's used to store "key" of the named list element in the storage. */
     public static final String NAME = "<name>";
 
     /** Configuration name for the synthetic key. */
     private final String syntheticKeyName;
 
     /** Supplier of new node objects when new list element node has to be created. */
-    private final Supplier<N> valSupplier;
+    private final Supplier<InnerNode> valSupplier;
 
-    /** Internal container for named list element. Maps keys to named list elements nodes with their internal ids. */
-    private final OrderedMap<ElementDescriptor<N>> map;
+    /** Internal container for named list element. Map keys to named list elements nodes with their internal ids. */
+    private final OrderedMap<ElementDescriptor> map;
 
     /** Mapping from internal ids to public keys. */
     private final Map<String, String> reverseIdMap;
 
+    /** Field name with {@link PolymorphicId}. */
+    @Nullable private final String typeIdFieldName;
+
     /**
      * Default constructor.
      *
      * @param syntheticKeyName Name of the synthetic configuration value that will represent keys in a specially ordered
      *      representation syntax.
      * @param valSupplier Closure to instantiate values.
+     * @param typeIdFieldName Field name with {@link PolymorphicId}.
      */
-    public NamedListNode(String syntheticKeyName, Supplier<N> valSupplier) {
+    public NamedListNode(String syntheticKeyName, Supplier<InnerNode> valSupplier, @Nullable String typeIdFieldName) {
         this.syntheticKeyName = syntheticKeyName;
         this.valSupplier = valSupplier;
+        this.typeIdFieldName = typeIdFieldName;
         map = new OrderedMap<>();
         reverseIdMap = new HashMap<>();
     }
@@ -81,6 +89,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
     private NamedListNode(NamedListNode<N> node) {
         syntheticKeyName = node.syntheticKeyName;
         valSupplier = node.valSupplier;
+        typeIdFieldName = node.typeIdFieldName;
         map = new OrderedMap<>();
         reverseIdMap = new HashMap<>(node.reverseIdMap);
 
@@ -94,20 +103,28 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
     }
 
     /** {@inheritDoc} */
-    @Override public final List<String> namedListKeys() {
+    @Override public List<String> namedListKeys() {
         return Collections.unmodifiableList(map.keys());
     }
 
     /** {@inheritDoc} */
-    @Override public final N get(String key) {
-        ElementDescriptor<N> element = map.get(key);
-
-        return element == null ? null : element.value;
+    @Override public N get(String key) {
+        return specificNode(map.get(key));
     }
 
     /** {@inheritDoc} */
     @Override public N get(int index) throws IndexOutOfBoundsException {
-        ElementDescriptor<N> element = map.get(index);
+        return specificNode(map.get(index));
+    }
+
+    /**
+     * Returns {@link InnerNode} associated with the passed key.
+     *
+     * @param key Key string.
+     * @return Requested value.
+     */
+    @Nullable public InnerNode getInnerNode(String key) {
+        ElementDescriptor element = map.get(key);
 
         return element == null ? null : element.value;
     }
@@ -124,13 +141,13 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
 
         checkNewKey(key);
 
-        ElementDescriptor<N> element = newElementDescriptor();
+        ElementDescriptor element = newElementDescriptor();
 
         map.put(key, element);
 
         reverseIdMap.put(element.internalId, key);
 
-        valConsumer.accept(element.value);
+        valConsumer.accept((N)element.value);
 
         return this;
     }
@@ -145,13 +162,13 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
 
         checkNewKey(key);
 
-        ElementDescriptor<N> element = newElementDescriptor();
+        ElementDescriptor element = newElementDescriptor();
 
         map.putByIndex(index, key, element);
 
         reverseIdMap.put(element.internalId, key);
 
-        valConsumer.accept(element.value);
+        valConsumer.accept((N)element.value);
 
         return this;
     }
@@ -167,26 +184,26 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
 
         checkNewKey(key);
 
-        ElementDescriptor<N> element = newElementDescriptor();
+        ElementDescriptor element = newElementDescriptor();
 
         map.putAfter(precedingKey, key, element);
 
         reverseIdMap.put(element.internalId, key);
 
-        valConsumer.accept(element.value);
+        valConsumer.accept((N)element.value);
 
         return this;
     }
 
     /** {@inheritDoc} */
-    @Override public final NamedListChange<N, N> createOrUpdate(String key, Consumer<N> valConsumer) {
+    @Override public NamedListChange<N, N> createOrUpdate(String key, Consumer<N> valConsumer) {
         Objects.requireNonNull(key, "key");
         Objects.requireNonNull(valConsumer, "valConsumer");
 
         if (map.containsKey(key) && map.get(key).value == null)
             throw new IllegalArgumentException("You can't create entity that has just been deleted [key=" + key + ']');
 
-        ElementDescriptor<N> element = map.get(key);
+        ElementDescriptor element = map.get(key);
 
         if (element == null) {
             element = newElementDescriptor();
@@ -198,7 +215,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
 
         map.put(key, element);
 
-        valConsumer.accept(element.value);
+        valConsumer.accept((N)element.value);
 
         return this;
     }
@@ -211,7 +228,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
         if (!map.containsKey(oldKey))
             throw new IllegalArgumentException("Element with name " + oldKey + " does not exist.");
 
-        ElementDescriptor<N> element = map.get(oldKey);
+        ElementDescriptor element = map.get(oldKey);
 
         if (element.value == null) {
             throw new IllegalArgumentException(
@@ -230,6 +247,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
 
     /**
      * Checks that this new key can be inserted into the map.
+     *
      * @param key New key.
      * @throws IllegalArgumentException If key already exists.
      */
@@ -263,13 +281,13 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
 
     /**
      * Sets an internal id for the value associated with the passed key. Should not be used in arbitrary code. Refer
-     * to {@link ConfigurationUtil#fillFromPrefixMap(ConstructableTreeNode, Map)} for further details on the usage.
+     * to {@link ConfigurationUtil#fillFromPrefixMap} for further details on the usage.
      *
      * @param key Key to update. Should be present in the named list. Nothing will happen if the key is missing.
      * @param internalId New id to associate with the key.
      */
     public void setInternalId(String key, String internalId) {
-        ElementDescriptor<N> element = map.get(key);
+        ElementDescriptor element = map.get(key);
 
         if (element != null) {
             reverseIdMap.remove(element.internalId);
@@ -289,7 +307,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
      * @throws IllegalArgumentException If {@code key} is not found in the named list.
      */
     public String internalId(String key) {
-        ElementDescriptor<N> element = map.get(key);
+        ElementDescriptor element = map.get(key);
 
         if (element == null)
             throw new IllegalArgumentException("Element with name '" + key + "' does not exist.");
@@ -322,7 +340,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
      * @param key Element's key.
      */
     public void forceDelete(String key) {
-        ElementDescriptor<N> removed = map.remove(key);
+        ElementDescriptor removed = map.remove(key);
 
         if (removed != null)
             reverseIdMap.remove(removed.internalId);
@@ -339,10 +357,51 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
 
     /** {@inheritDoc} */
     @Override public void construct(String key, ConfigurationSource src, boolean includeInternal) {
+        Objects.requireNonNull(key, "key");
+
         if (src == null)
             delete(key);
-        else
-            createOrUpdate(key, src::descend);
+        else {
+            if (map.containsKey(key) && map.get(key).value == null)
+                throw new IllegalArgumentException("You can't create entity that has just been deleted [key=" + key + ']');
+
+            ElementDescriptor element = map.get(key);
+
+            if (element == null) {
+                element = newElementDescriptor();
+
+                reverseIdMap.put(element.internalId, key);
+
+                if (typeIdFieldName != null) {
+                    InnerNode polymorphicInnerNode = element.value;
+
+                    String polymorphicTypeId = src.polymorphicTypeId(typeIdFieldName);
+
+                    if (polymorphicTypeId != null)
+                        polymorphicInnerNode.construct(typeIdFieldName, new LeafConfigurationSource(polymorphicTypeId), true);
+                    else if (polymorphicInnerNode.traverseChild(typeIdFieldName, leafNodeVisitor(), true) == null) {
+                        throw new IllegalStateException("Polymorphic configuration type is not defined: " +
+                            polymorphicInnerNode.getClass().getName());
+                    }
+                }
+            }
+            else {
+                element = element.copy();
+
+                if (typeIdFieldName != null) {
+                    InnerNode polymorphicInnerNode = element.value;
+
+                    String polymorphicTypeId = src.polymorphicTypeId(typeIdFieldName);
+
+                    if (polymorphicTypeId != null)
+                        polymorphicInnerNode.construct(typeIdFieldName, new LeafConfigurationSource(polymorphicTypeId), true);
+                }
+            }
+
+            map.put(key, element);
+
+            src.descend(element.value);
+        }
     }
 
     /** {@inheritDoc} */
@@ -355,32 +414,30 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
      *
      * @return New element instance with initialized defaults.
      */
-    private NamedListNode.ElementDescriptor<N> newElementDescriptor() {
-        N newElement = valSupplier.get();
+    private NamedListNode.ElementDescriptor newElementDescriptor() {
+        InnerNode newElement = valSupplier.get();
 
         addDefaults(newElement);
 
-        return new ElementDescriptor<>(newElement);
+        return new ElementDescriptor(newElement);
     }
 
     /**
      * Descriptor for internal named list element representation. Has node itself and its internal id.
-     *
-     * @param <N> Type of the node.
      */
-    private static class ElementDescriptor<N extends InnerNode> {
+    private static class ElementDescriptor {
         /** Element's internal id. */
         public String internalId;
 
         /** Element node value. */
-        public N value;
+        @Nullable public InnerNode value;
 
         /**
          * Constructor.
          *
          * @param value Node instance.
          */
-        ElementDescriptor(N value) {
+        ElementDescriptor(InnerNode value) {
             this.value = value;
             // Remove dashes so that id would be a bit shorter and easier to validate in tests.
             // This string won't be visible by end users anyway.
@@ -393,7 +450,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
          * @param internalId Internal id.
          * @param value Node instance.
          */
-        private ElementDescriptor(String internalId, N value) {
+        private ElementDescriptor(String internalId, InnerNode value) {
             this.internalId = internalId;
             this.value = value;
         }
@@ -404,8 +461,8 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
          * @return New instance with the same internal id but copied node instance.
          * @see InnerNode#copy()
          */
-        public ElementDescriptor<N> copy() {
-            return new ElementDescriptor<>(internalId, (N)value.copy());
+        public ElementDescriptor copy() {
+            return new ElementDescriptor(internalId, value.copy());
         }
 
         /**
@@ -413,8 +470,23 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
          *
          * @return New instance with the same internal id and node instance.
          */
-        public ElementDescriptor<N> shallowCopy() {
-            return new ElementDescriptor<>(internalId, value);
+        public ElementDescriptor shallowCopy() {
+            return new ElementDescriptor(internalId, value);
         }
     }
+
+    /**
+     * Returns specific {@code Node}.
+     *
+     * @param element Internal named list element representation.
+     * @return Specific {@code Node}.
+     */
+    @Nullable private N specificNode(@Nullable ElementDescriptor element) {
+        if (element == null)
+            return null;
+
+        InnerNode value = element.value;
+
+        return value == null ? null : value.specificNode();
+    }
 }
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 c8e359d..531672a 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
@@ -47,7 +47,7 @@ public class ConfigurationFlattener {
 
         oldInnerNodesStack.push(curRoot);
 
-        // Explicit access to the children of super root guarantees that "oldInnerNodesStack" is never empty and thus
+        // 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), true);
 
@@ -66,7 +66,7 @@ public class ConfigurationFlattener {
         int idx = 0;
 
         for (String key : node.namedListKeys()) {
-            if (node.get(key) != null)
+            if (node.getInnerNode(key) != null)
                 res.put(key, idx++);
         }
 
@@ -92,6 +92,12 @@ public class ConfigurationFlattener {
          */
         private boolean deletion;
 
+        /**
+         * Constructor.
+         *
+         * @param oldInnerNodesStack Old nodes stack for recursion.
+         * @param resMap Map with the result.
+         */
         FlattenerVisitor(Deque<InnerNode> oldInnerNodesStack, Map<String, Serializable> resMap) {
             this.oldInnerNodesStack = oldInnerNodesStack;
             this.resMap = resMap;
@@ -120,6 +126,14 @@ public class ConfigurationFlattener {
 
             if (oldNode == null)
                 visitAsymmetricInnerNode(newNode, false);
+            else if (oldNode.schemaType() != newNode.schemaType()) {
+                // At the moment, we do not separate the general fields from the fields of
+                // specific instances of the polymorphic configuration, so we will assume
+                // that all the fields have changed, perhaps we will fix this later.
+                visitAsymmetricInnerNode(oldNode, true);
+
+                visitAsymmetricInnerNode(newNode, false);
+            }
             else {
                 oldInnerNodesStack.push(oldNode);
 
@@ -132,7 +146,7 @@ public class ConfigurationFlattener {
         }
 
         /** {@inheritDoc} */
-        @Override public <N extends InnerNode> Void doVisitNamedListNode(String key, NamedListNode<N> newNode) {
+        @Override public Void doVisitNamedListNode(String key, NamedListNode<?> newNode) {
             // Read same named list node from old tree.
             NamedListNode<?> oldNode = oldInnerNodesStack.peek().traverseChild(key, ConfigurationUtil.namedListNodeVisitor(), true);
 
@@ -152,10 +166,10 @@ public class ConfigurationFlattener {
                 String newNodeInternalId = newNode.internalId(newNodeKey);
 
                 withTracking(newNodeInternalId, false, false, () -> {
-                    InnerNode newNamedElement = newNode.get(newNodeKey);
+                    InnerNode newNamedElement = newNode.getInnerNode(newNodeKey);
 
                     String oldNodeKey = oldNode.keyByInternalId(newNodeInternalId);
-                    InnerNode oldNamedElement = oldNode.get(oldNodeKey);
+                    InnerNode oldNamedElement = oldNode.getInnerNode(oldNodeKey);
 
                     // Deletion of nonexistent element.
                     if (oldNamedElement == null && newNamedElement == null)
@@ -168,6 +182,14 @@ public class ConfigurationFlattener {
                             visitAsymmetricInnerNode(oldNamedElement, true);
                         else if (oldNamedElement == null)
                             visitAsymmetricInnerNode(newNamedElement, false);
+                        else if (newNamedElement.schemaType() != oldNamedElement.schemaType()) {
+                            // At the moment, we do not separate the general fields from the fields of
+                            // specific instances of the polymorphic configuration, so we will assume
+                            // that all the fields have changed, perhaps we will fix this later.
+                            visitAsymmetricInnerNode(oldNamedElement, true);
+
+                            visitAsymmetricInnerNode(newNamedElement, false);
+                        }
                         else {
                             oldInnerNodesStack.push(oldNamedElement);
 
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 1a1c3f2..836b600 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
@@ -115,6 +115,12 @@ public class ConfigurationNotificationsUtil {
             eventConfigs
         );
 
+        // Polymorphic configuration type has changed.
+        // At the moment, we do not separate common fields from fields of a specific polymorphic configuration,
+        // so this may cause errors in the logic below, perhaps we will fix it later.
+        if (oldInnerNode.schemaType() != newInnerNode.schemaType())
+            return;
+
         oldInnerNode.traverseChildren(new ConfigurationVisitor<Void>() {
             /** {@inheritDoc} */
             @Override public Void visitLeafNode(String key, Serializable oldLeaf) {
@@ -238,7 +244,7 @@ public class ConfigurationNotificationsUtil {
 
                         eventConfigs.put(newNodeCfg.configType(), new ConfigurationContainer(name, newNodeCfg));
 
-                        InnerNode newVal = newNamedList.get(name);
+                        InnerNode newVal = newNamedList.getInnerNode(name);
 
                         for (DynamicConfiguration<InnerNode, ?> anyConfig : anyConfigs) {
                             notifyPublicListeners(
@@ -287,7 +293,7 @@ public class ConfigurationNotificationsUtil {
 
                         eventConfigs.put(delNodeCfg.configType(), new ConfigurationContainer(name, null));
 
-                        InnerNode oldVal = oldNamedList.get(name);
+                        InnerNode oldVal = oldNamedList.getInnerNode(name);
 
                         for (DynamicConfiguration<InnerNode, ?> anyConfig : anyConfigs) {
                             notifyPublicListeners(
@@ -347,8 +353,8 @@ public class ConfigurationNotificationsUtil {
                             new ConfigurationContainer(entry.getValue(), renNodeCfg)
                         );
 
-                        InnerNode oldVal = oldNamedList.get(entry.getKey());
-                        InnerNode newVal = newNamedList.get(entry.getValue());
+                        InnerNode oldVal = oldNamedList.getInnerNode(entry.getKey());
+                        InnerNode newVal = newNamedList.getInnerNode(entry.getValue());
 
                         for (DynamicConfiguration<InnerNode, ?> anyConfig : anyConfigs) {
                             notifyPublicListeners(
@@ -379,8 +385,8 @@ public class ConfigurationNotificationsUtil {
                     updated.retainAll(oldNames);
 
                     for (String name : updated) {
-                        InnerNode oldVal = oldNamedList.get(name);
-                        InnerNode newVal = newNamedList.get(name);
+                        InnerNode oldVal = oldNamedList.getInnerNode(name);
+                        InnerNode newVal = newNamedList.getInnerNode(name);
 
                         if (oldVal == newVal)
                             continue;
@@ -594,7 +600,7 @@ public class ConfigurationNotificationsUtil {
 
                     configs.put(newNodeCfg.configType(), new ConfigurationContainer(name, newNodeCfg));
 
-                    InnerNode newVal = newNamedList.get(name);
+                    InnerNode newVal = newNamedList.getInnerNode(name);
 
                     for (DynamicConfiguration<InnerNode, ?> anyConfig : anyConfigs) {
                         notifyPublicListeners(
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 7efc849..90ab341 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,6 +18,7 @@
 package org.apache.ignite.internal.configuration.util;
 
 import java.io.Serializable;
+import java.lang.annotation.Annotation;
 import java.lang.reflect.Field;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -25,6 +26,8 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
@@ -34,6 +37,7 @@ import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.ignite.configuration.ConfigurationProperty;
+import org.apache.ignite.configuration.ConfigurationWrongPolymorphicTypeIdException;
 import org.apache.ignite.configuration.DirectConfigurationProperty;
 import org.apache.ignite.configuration.NamedListView;
 import org.apache.ignite.configuration.RootKey;
@@ -43,6 +47,9 @@ import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.annotation.DirectAccess;
 import org.apache.ignite.configuration.annotation.InternalConfiguration;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
@@ -51,8 +58,11 @@ import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
 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 org.jetbrains.annotations.Nullable;
 
+import static java.util.function.Function.identity;
 import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
 
 /** */
 public class ConfigurationUtil {
@@ -116,12 +126,13 @@ public class ConfigurationUtil {
      * @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.
+     * @throws WrongPolymorphicTypeIdException If the type of the polymorphic configuration instance is not correct (unknown).
      */
     public static <T> T find(
         List<String> keys,
         TraversableTreeNode node,
         boolean includeInternal
-    ) throws KeyNotFoundException {
+    ) throws KeyNotFoundException, WrongPolymorphicTypeIdException {
         assert keys instanceof RandomAccess : keys.getClass();
 
         var visitor = new ConfigurationVisitor<T>() {
@@ -151,6 +162,12 @@ public class ConfigurationUtil {
                             "Configuration value '" + join(keys.subList(0, i)) + "' has not been found"
                         );
                     }
+                    catch (ConfigurationWrongPolymorphicTypeIdException e) {
+                        throw new WrongPolymorphicTypeIdException(
+                            "Polymorphic configuration type is not correct: " + e.getMessage(),
+                            e
+                        );
+                    }
                 }
             }
 
@@ -161,7 +178,7 @@ public class ConfigurationUtil {
                 else {
                     String name = keys.get(i++);
 
-                    return visitInnerNode(name, node.get(name));
+                    return visitInnerNode(name, node.getInnerNode(name));
                 }
             }
         };
@@ -235,178 +252,8 @@ public class ConfigurationUtil {
      * @throws UnsupportedOperationException if prefix map structure doesn't correspond to actual tree structure.
      *      This will be fixed when method is actually used in configuration storage intergration.
      */
-    public static void fillFromPrefixMap(ConstructableTreeNode node, Map<String, ?> prefixMap) {
-        assert node instanceof InnerNode;
-
-        /** */
-        class LeafConfigurationSource implements ConfigurationSource {
-            /** */
-            private final Serializable val;
-
-            /**
-             * @param val Value.
-             */
-            private LeafConfigurationSource(Serializable val) {
-                this.val = val;
-            }
-
-            /** {@inheritDoc} */
-            @Override public <T> T unwrap(Class<T> clazz) {
-                assert val == null || clazz.isInstance(val);
-
-                return clazz.cast(val);
-            }
-
-            /** {@inheritDoc} */
-            @Override public void descend(ConstructableTreeNode node) {
-                throw new UnsupportedOperationException("descend");
-            }
-        }
-
-        /** */
-        class InnerConfigurationSource implements ConfigurationSource {
-            /** */
-            private final Map<String, ?> map;
-
-            /**
-             * @param map Prefix map.
-             */
-            private InnerConfigurationSource(Map<String, ?> map) {
-                this.map = map;
-            }
-
-            /** {@inheritDoc} */
-            @Override public <T> T unwrap(Class<T> clazz) {
-                throw new UnsupportedOperationException("unwrap");
-            }
-
-            /** {@inheritDoc} */
-            @Override public void descend(ConstructableTreeNode node) {
-                if (node instanceof NamedListNode) {
-                    descendToNamedListNode((NamedListNode<?>)node);
-
-                    return;
-                }
-
-                for (Map.Entry<String, ?> entry : map.entrySet()) {
-                    String key = entry.getKey();
-                    Object val = entry.getValue();
-
-                    assert val == null || val instanceof Map || val instanceof Serializable;
-
-                    // Ordering of indexes must be skipped here because they make no sense in this context.
-                    if (key.equals(NamedListNode.ORDER_IDX) || key.equals(NamedListNode.NAME))
-                        continue;
-
-                    if (val == null)
-                        node.construct(key, null, true);
-                    else if (val instanceof Map)
-                        node.construct(key, new InnerConfigurationSource((Map<String, ?>)val), true);
-                    else {
-                        assert val instanceof Serializable;
-
-                        node.construct(key, new LeafConfigurationSource((Serializable)val), true);
-                    }
-                }
-            }
-
-            /**
-             * Specific implementation of {@link #descend(ConstructableTreeNode)} that descends into named list node and
-             * sets a proper ordering to named list elements.
-             *
-             * @param node Named list node under construction.
-             */
-            private void descendToNamedListNode(NamedListNode<?> node) {
-                // This list must be mutable and RandomAccess.
-                var orderedKeys = new ArrayList<>(((NamedListView<?>)node).namedListKeys());
-
-                for (Map.Entry<String, ?> entry : map.entrySet()) {
-                    String internalId = entry.getKey();
-                    Object val = entry.getValue();
-
-                    assert val == null || val instanceof Map || val instanceof Serializable;
-
-                    String oldKey = node.keyByInternalId(internalId);
-
-                    if (val == null) {
-                        // Given that this particular method is applied to modify existing trees rather than
-                        // creating new trees, a "hack" is required in this place. "construct" is designed to create
-                        // "change" objects, thus it would just nullify named list element instead of deleting it.
-                        node.forceDelete(oldKey);
-                    }
-                    else if (val instanceof Map) {
-                        Map<String, ?> map = (Map<String, ?>)val;
-                        int sizeDiff = 0;
-
-                        // For every named list entry modification we must take its index into account.
-                        // We do this by modifying "orderedKeys" when index is explicitly passed.
-                        Object idxObj = map.get(NamedListNode.ORDER_IDX);
-
-                        if (idxObj != null)
-                            sizeDiff++;
-
-                        String newKey = (String)map.get(NamedListNode.NAME);
-
-                        if (newKey != null)
-                            sizeDiff++;
-
-                        boolean construct = map.size() != sizeDiff;
-
-                        if (oldKey == null) {
-                            node.construct(newKey, new InnerConfigurationSource(map), true);
-
-                            node.setInternalId(newKey, internalId);
-                        }
-                        else if (newKey != null) {
-                            node.rename(oldKey, newKey);
-
-                            if (construct)
-                                node.construct(newKey, new InnerConfigurationSource(map), true);
-                        }
-                        else if (construct)
-                            node.construct(oldKey, new InnerConfigurationSource(map), true);
-                        // Else it's just index adjustment after new elements insertion.
-
-                        if (newKey == null)
-                            newKey = oldKey;
-
-                        if (idxObj != null) {
-                            assert idxObj instanceof Integer : val;
-
-                            int idx = (Integer)idxObj;
-
-                            if (idx >= orderedKeys.size()) {
-                                // Updates can come in arbitrary order. This means that array may be too small
-                                // during batch creation. In this case we have to insert enough nulls before
-                                // invoking "add" method for actual key.
-                                orderedKeys.ensureCapacity(idx + 1);
-
-                                while (idx != orderedKeys.size())
-                                    orderedKeys.add(null);
-
-                                orderedKeys.add(newKey);
-                            }
-                            else
-                                orderedKeys.set(idx, newKey);
-                        }
-                    }
-                    else {
-                        assert val instanceof Serializable;
-
-                        node.construct(oldKey, new LeafConfigurationSource((Serializable)val), true);
-                    }
-                }
-
-                node.reorderKeys(orderedKeys.size() > node.size()
-                    ? orderedKeys.subList(0, node.size())
-                    : orderedKeys
-                );
-            }
-        }
-
-        var src = new InnerConfigurationSource(prefixMap);
-
-        src.descend(node);
+    public static void fillFromPrefixMap(InnerNode node, Map<String, ?> prefixMap) {
+        new InnerConfigurationSource(prefixMap).descend(node);
     }
 
     /**
@@ -436,7 +283,7 @@ public class ConfigurationUtil {
         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 source value is null then initialise the same value on the destination node.
                 if (val == null)
                     node.constructDefault(key);
 
@@ -464,11 +311,11 @@ public class ConfigurationUtil {
                 namedList = node.traverseChild(key, namedListNodeVisitor(), true);
 
                 for (String namedListKey : namedList.namedListKeys()) {
-                    if (namedList.get(namedListKey) != null) {
+                    if (namedList.getInnerNode(namedListKey) != null) {
                         // Copy the element.
                         namedList.construct(namedListKey, EMPTY_CFG_SRC, true);
 
-                        addDefaults(namedList.get(namedListKey));
+                        addDefaults(namedList.getInnerNode(namedListKey));
                     }
                 }
 
@@ -492,7 +339,7 @@ public class ConfigurationUtil {
 
             @Override public Object visitNamedListNode(String key, NamedListNode<?> namedList) {
                 for (String namedListKey : namedList.namedListKeys()) {
-                    InnerNode element = namedList.get(namedListKey);
+                    InnerNode element = namedList.getInnerNode(namedListKey);
 
                     if (element == null)
                         namedList.forceDelete(namedListKey);
@@ -554,35 +401,6 @@ 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.
@@ -637,125 +455,431 @@ public class ConfigurationUtil {
     }
 
     /**
-     * Get the default value of a {@link Value}.
+     * Collect all configuration schemas with {@link ConfigurationRoot}, {@link Config} or {@link PolymorphicConfig}
+     * including all sub configuration schemas for fields with {@link ConfigValue} or {@link NamedConfigValue}.
      *
-     * @param field Configuration Schema class field.
-     * @return Default value.
+     * @param schemaClasses Configuration schemas (starting points) with {@link ConfigurationRoot}, {@link Config}
+     *      or {@link PolymorphicConfig}.
+     * @return All configuration schemas with {@link ConfigurationRoot}, {@link Config}, or {@link PolymorphicConfig}.
+     * @throws IllegalArgumentException If the configuration schemas does not contain
+     *      {@link ConfigurationRoot}, {@link Config} or {@link PolymorphicConfig}.
      */
-    public static Object defaultValue(Field field) {
-        assert hasDefault(field) : field;
+    public static Set<Class<?>> collectSchemas(Collection<Class<?>> schemaClasses) {
+        if (schemaClasses.isEmpty())
+            return Set.of();
 
-        try {
-            Object o = field.getDeclaringClass().getDeclaredConstructor().newInstance();
+        Set<Class<?>> res = new HashSet<>();
 
-            field.setAccessible(true);
+        Queue<Class<?>> queue = new ArrayDeque<>(Set.copyOf(schemaClasses));
 
-            return field.get(o);
-        }
-        catch (ReflectiveOperationException e) {
-            throw new RuntimeException(e);
+        while (!queue.isEmpty()) {
+            Class<?> cls = queue.poll();
+
+            if (!cls.isAnnotationPresent(ConfigurationRoot.class) && !cls.isAnnotationPresent(Config.class) &&
+                !cls.isAnnotationPresent(PolymorphicConfig.class)) {
+                throw new IllegalArgumentException(String.format(
+                    "Configuration schema must contain @%s or @%s or @%s: %s",
+                    ConfigurationRoot.class.getSimpleName(),
+                    Config.class.getSimpleName(),
+                    PolymorphicConfig.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());
+    }
+
+    /**
+     * Extracts the "direct" value from the given property.
+     *
+     * @param property Property to get the value from.
+     * @return "direct" value of the property.
+     * @throws ClassCastException if the property has not been annotated with {@link DirectAccess}.
+     *
+     * @see DirectAccess
+     * @see DirectConfigurationProperty
+     */
+    public static <T> T directValue(ConfigurationProperty<T> property) {
+        return ((DirectConfigurationProperty<T>)property).directValue();
+    }
+
+    /**
+     * Get configuration schemas and their validated internal extensions.
+     *
+     * @param extensions Schema extensions with {@link InternalConfiguration}.
+     * @return Mapping: original of the scheme -> internal schema extensions.
+     * @throws IllegalArgumentException If the schema extension is invalid.
+     * @see InternalConfiguration
+     */
+    public static Map<Class<?>, Set<Class<?>>> internalSchemaExtensions(Collection<Class<?>> extensions) {
+        return schemaExtensions(extensions, InternalConfiguration.class);
+    }
+
+    /**
+     * Get polymorphic extensions of configuration schemas.
+     *
+     * @param extensions Schema extensions with {@link PolymorphicConfigInstance}.
+     * @return Mapping: polymorphic scheme -> extensions (instances) of polymorphic configuration.
+     * @throws IllegalArgumentException If the schema extension is invalid.
+     * @see PolymorphicConfig
+     * @see PolymorphicConfigInstance
+     */
+    public static Map<Class<?>, Set<Class<?>>> polymorphicSchemaExtensions(Collection<Class<?>> extensions) {
+        return schemaExtensions(extensions, PolymorphicConfigInstance.class);
     }
 
     /**
-     * Get merged schema extension fields.
+     * Get configuration schemas and their validated extensions.
+     * Configuration schema is the parent class of the extension.
      *
-     * @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.
+     * @param extensions Schema extensions.
+     * @param annotationClass Annotation class that the extension should have.
+     * @return Mapping: original of the scheme -> schema extensions.
+     * @throws IllegalArgumentException If the schema extension is invalid.
      */
-    public static Set<Field> extensionsFields(Collection<Class<?>> extensions) {
+    private static Map<Class<?>, Set<Class<?>>> schemaExtensions(
+        Collection<Class<?>> extensions,
+        Class<? extends Annotation> annotationClass
+    ) {
         if (extensions.isEmpty())
-            return Set.of();
-        else {
-            Map<String, Field> res = new HashMap<>();
+            return Map.of();
+
+        Map<Class<?>, Set<Class<?>>> res = new HashMap<>();
+
+        for (Class<?> extension : extensions) {
+            if (!extension.isAnnotationPresent(annotationClass)) {
+                throw new IllegalArgumentException(String.format(
+                    "Extension should contain @%s: %s",
+                    annotationClass.getSimpleName(),
+                    extension.getName()
+                ));
+            }
+            else
+                res.computeIfAbsent(extension.getSuperclass(), cls -> new HashSet<>()).add(extension);
+        }
 
-            for (Class<?> extension : extensions) {
-                assert extension.isAnnotationPresent(InternalConfiguration.class) : extension;
+        return res;
+    }
 
-                for (Field field : extension.getDeclaredFields()) {
-                    String fieldName = field.getName();
+    /**
+     * Collects fields of configuration schema that contain {@link Value}, {@link ConfigValue},
+     *      {@link NamedConfigValue} or {@link PolymorphicId}.
+     *
+     * @param schemaClass Configuration schema class.
+     * @return Schema fields.
+     */
+    public static List<Field> schemaFields(Class<?> schemaClass) {
+        return Arrays.stream(schemaClass.getDeclaredFields())
+            .filter(f -> isValue(f) || isConfigValue(f) || isNamedConfigValue(f) || isPolymorphicId(f))
+            .collect(toList());
+    }
 
-                    if (res.containsKey(fieldName)) {
+    /**
+     * Collects fields of configuration schema extensions that contain {@link Value}, {@link ConfigValue},
+     *      {@link NamedConfigValue} or {@link PolymorphicId}.
+     *
+     * @param extensions Configuration schema extensions.
+     * @param uniqueByName Checking the uniqueness of fields by {@link Field#getName name}.
+     * @return Schema extensions fields.
+     * @throws IllegalArgumentException If there was a field name conflict for {@code uniqueByName == true}.
+     */
+    public static Collection<Field> extensionsFields(Collection<Class<?>> extensions, boolean uniqueByName) {
+        if (extensions.isEmpty())
+            return List.of();
+
+        if (uniqueByName) {
+            return extensions.stream()
+                .flatMap(cls -> Arrays.stream(cls.getDeclaredFields()))
+                .filter(f -> isValue(f) || isConfigValue(f) || isNamedConfigValue(f) || isPolymorphicId(f))
+                .collect(toMap(
+                    Field::getName,
+                    identity(),
+                    (f1, f2) -> {
                         throw new IllegalArgumentException(String.format(
                             "Duplicate field names are not allowed [field=%s, classes=%s]",
-                            field,
-                            classNames(res.get(fieldName), field)
+                            f1.getName(),
+                            classNames(f1, f2)
                         ));
-                    }
-                    else
-                        res.put(fieldName, field);
-                }
-            }
-
-            return Set.copyOf(res.values());
+                    },
+                    LinkedHashMap::new
+                )).values();
+        }
+        else {
+            return extensions.stream()
+                .flatMap(cls -> Arrays.stream(cls.getDeclaredFields()))
+                .filter(f -> isValue(f) || isConfigValue(f) || isNamedConfigValue(f) || isPolymorphicId(f))
+                .collect(toList());
         }
     }
 
     /**
-     * Collect all configuration schemes with {@link ConfigurationRoot} or {@link Config}
-     * including all sub configuration schemes for fields with {@link ConfigValue} or {@link NamedConfigValue}.
+     * Checks whether configuration schema field contains {@link PolymorphicId}.
      *
-     * @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}.
+     * @param schemaField Configuration schema class field.
+     * @return {@code true} if the field contains {@link PolymorphicId}.
      */
-    public static Set<Class<?>> collectSchemas(Collection<Class<?>> schemaClasses) {
-        if (schemaClasses.isEmpty())
-            return Set.of();
-        else {
-            Set<Class<?>> res = new HashSet<>();
+    public static boolean isPolymorphicId(Field schemaField) {
+        return schemaField.isAnnotationPresent(PolymorphicId.class);
+    }
 
-            Queue<Class<?>> queue = new ArrayDeque<>(Set.copyOf(schemaClasses));
+    /**
+     * Checks whether configuration schema contains {@link PolymorphicConfig}.
+     *
+     * @param schemaClass Configuration schema class.
+     * @return {@code true} if the schema contains {@link PolymorphicConfig}.
+     */
+    public static boolean isPolymorphicConfig(Class<?> schemaClass) {
+        return schemaClass.isAnnotationPresent(PolymorphicConfig.class);
+    }
 
-            while (!queue.isEmpty()) {
-                Class<?> cls = queue.poll();
+    /**
+     * Checks whether configuration schema contains {@link PolymorphicConfigInstance}.
+     *
+     * @param schemaClass Configuration schema class.
+     * @return {@code true} if the schema contains {@link PolymorphicConfigInstance}.
+     */
+    public static boolean isPolymorphicConfigInstance(Class<?> schemaClass) {
+        return schemaClass.isAnnotationPresent(PolymorphicConfigInstance.class);
+    }
 
-                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);
+    /**
+     * Returns the identifier of the polymorphic configuration.
+     *
+     * @param schemaClass Configuration schema class.
+     * @return Identifier of the polymorphic configuration.
+     * @see PolymorphicConfigInstance#value
+     */
+    public static String polymorphicInstanceId(Class<?> schemaClass) {
+        assert isPolymorphicConfigInstance(schemaClass) : schemaClass.getName();
 
-                    for (Field f : cls.getDeclaredFields()) {
-                        if ((f.isAnnotationPresent(ConfigValue.class) || f.isAnnotationPresent(NamedConfigValue.class))
-                            && !res.contains(f.getType()))
-                            queue.add(f.getType());
-                    }
-                }
+        return schemaClass.getAnnotation(PolymorphicConfigInstance.class).value();
+    }
+
+    /**
+     * Prepares a map for further work with it:
+     * 1)If a deleted element of the named list is encountered, then this subtree becomes {@code null};
+     * 2)If a {@code null} leaf is encountered due to a change in the polymorphic configuration, then remove it.
+     *
+     * @param prefixMap Prefix map, constructed from the storage notification data or its subtree.
+     */
+    public static void compressDeletedEntries(Map<String, ?> prefixMap) {
+        for (Iterator<? extends Map.Entry<String, ?>> it = prefixMap.entrySet().iterator(); it.hasNext(); ) {
+            Map.Entry<String, ?> entry = it.next();
+
+            Object value = entry.getValue();
+
+            if (value instanceof Map) {
+                Map<?, ?> map = (Map<?, ?>)value;
+
+                // If an element of the named list is removed then {@link NamedListNode#NAME}
+                // will be {@code null} and the entire subtree can be replaced with {@code null}.
+                if (map.containsKey(NamedListNode.NAME) && map.get(NamedListNode.NAME) == null)
+                    entry.setValue(null);
             }
+            else if (value == null) {
+                // If there was a change in the type of polymorphic configuration,
+                // then the fields of the old configuration will be {@code null}, so we can get rid of them.
+                it.remove();
+            }
+        }
 
-            return res;
+        // Continue recursively.
+        for (Object value : prefixMap.values()) {
+            if (value instanceof Map)
+                compressDeletedEntries((Map<String, ?>)value);
         }
     }
 
     /**
-     * Get the class names of the fields.
-     *
-     * @param fields Fields.
-     * @return Fields class names.
+     * Leaf configuration source.
      */
-    public static List<String> classNames(Field... fields) {
-        return Stream.of(fields).map(Field::getDeclaringClass).map(Class::getName).collect(toList());
+    public static class LeafConfigurationSource implements ConfigurationSource {
+        /** Value. */
+        private final Serializable val;
+
+        /**
+         * Constructor.
+         *
+         * @param val Value.
+         */
+        public LeafConfigurationSource(Serializable val) {
+            this.val = val;
+        }
+
+        /** {@inheritDoc} */
+        @Override public <T> T unwrap(Class<T> clazz) {
+            assert val == null || clazz.isInstance(val);
+
+            return clazz.cast(val);
+        }
+
+        /** {@inheritDoc} */
+        @Override public void descend(ConstructableTreeNode node) {
+            throw new UnsupportedOperationException("descend");
+        }
     }
 
     /**
-     * Extracts the "direct" value from the given property.
-     *
-     * @param property Property to get the value from.
-     * @return "direct" value of the property.
-     * @throws ClassCastException if the property has not been annotated with {@link DirectAccess}.
-     *
-     * @see DirectAccess
-     * @see DirectConfigurationProperty
+     * Inner configuration source.
      */
-    public static <T> T directValue(ConfigurationProperty<T> property) {
-        return ((DirectConfigurationProperty<T>)property).directValue();
+    private static class InnerConfigurationSource implements ConfigurationSource {
+        /** Prefix map. */
+        private final Map<String, ?> map;
+
+        /**
+         * Constructor.
+         *
+         * @param map Prefix map.
+         */
+        private InnerConfigurationSource(Map<String, ?> map) {
+            this.map = map;
+        }
+
+        /** {@inheritDoc} */
+        @Override public <T> T unwrap(Class<T> clazz) {
+            throw new UnsupportedOperationException("unwrap");
+        }
+
+        /** {@inheritDoc} */
+        @Override public void descend(ConstructableTreeNode node) {
+            if (node instanceof NamedListNode) {
+                descendToNamedListNode((NamedListNode<?>)node);
+
+                return;
+            }
+
+            for (Map.Entry<String, ?> entry : map.entrySet()) {
+                String key = entry.getKey();
+                Object val = entry.getValue();
+
+                assert val == null || val instanceof Map || val instanceof Serializable;
+
+                // Ordering of indexes must be skipped here because they make no sense in this context.
+                if (key.equals(NamedListNode.ORDER_IDX) || key.equals(NamedListNode.NAME))
+                    continue;
+
+                if (val == null)
+                    node.construct(key, null, true);
+                else if (val instanceof Map)
+                    node.construct(key, new InnerConfigurationSource((Map<String, ?>)val), true);
+                else
+                    node.construct(key, new LeafConfigurationSource((Serializable)val), true);
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override public @Nullable String polymorphicTypeId(String fieldName) {
+            return (String)map.get(fieldName);
+        }
+
+        /**
+         * Specific implementation of {@link #descend(ConstructableTreeNode)} that descends into named list node and
+         * sets a proper ordering to named list elements.
+         *
+         * @param node Named list node under construction.
+         */
+        private void descendToNamedListNode(NamedListNode<?> node) {
+            // This list must be mutable and RandomAccess.
+            var orderedKeys = new ArrayList<>(((NamedListView<?>)node).namedListKeys());
+
+            for (Map.Entry<String, ?> entry : map.entrySet()) {
+                String internalId = entry.getKey();
+                Object val = entry.getValue();
+
+                assert val == null || val instanceof Map || val instanceof Serializable;
+
+                String oldKey = node.keyByInternalId(internalId);
+
+                if (val == null) {
+                    // Given that this particular method is applied to modify existing trees rather than
+                    // creating new trees, a "hack" is required in this place. "construct" is designed to create
+                    // "change" objects, thus it would just nullify named list element instead of deleting it.
+                    node.forceDelete(oldKey);
+                }
+                else if (val instanceof Map) {
+                    Map<String, ?> map = (Map<String, ?>)val;
+                    int sizeDiff = 0;
+
+                    // For every named list entry modification we must take its index into account.
+                    // We do this by modifying "orderedKeys" when index is explicitly passed.
+                    Object idxObj = map.get(NamedListNode.ORDER_IDX);
+
+                    if (idxObj != null)
+                        sizeDiff++;
+
+                    String newKey = (String)map.get(NamedListNode.NAME);
+
+                    if (newKey != null)
+                        sizeDiff++;
+
+                    boolean construct = map.size() != sizeDiff;
+
+                    if (oldKey == null) {
+                        node.construct(newKey, new InnerConfigurationSource(map), true);
+
+                        node.setInternalId(newKey, internalId);
+                    }
+                    else if (newKey != null) {
+                        node.rename(oldKey, newKey);
+
+                        if (construct)
+                            node.construct(newKey, new InnerConfigurationSource(map), true);
+                    }
+                    else if (construct)
+                        node.construct(oldKey, new InnerConfigurationSource(map), true);
+                    // Else it's just index adjustment after new elements insertion.
+
+                    if (newKey == null)
+                        newKey = oldKey;
+
+                    if (idxObj != null) {
+                        assert idxObj instanceof Integer : val;
+
+                        int idx = (Integer)idxObj;
+
+                        if (idx >= orderedKeys.size()) {
+                            // Updates can come in arbitrary order. This means that array may be too small
+                            // during batch creation. In this case we have to insert enough nulls before
+                            // invoking "add" method for actual key.
+                            orderedKeys.ensureCapacity(idx + 1);
+
+                            while (idx != orderedKeys.size())
+                                orderedKeys.add(null);
+
+                            orderedKeys.add(newKey);
+                        }
+                        else
+                            orderedKeys.set(idx, newKey);
+                    }
+                }
+                else
+                    node.construct(oldKey, new LeafConfigurationSource((Serializable)val), true);
+            }
+
+            node.reorderKeys(orderedKeys.size() > node.size()
+                ? orderedKeys.subList(0, node.size())
+                : orderedKeys
+            );
+        }
     }
 }
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 17d07ae..fc150f5 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
@@ -99,15 +99,14 @@ public abstract class KeysTrackingConfigurationVisitor<T> implements Configurati
      *
      * @param key Name of the node retrieved from its holder object.
      * @param node Named list inner configuration node.
-     * @param <N> Type of element nodes in the named list.
      * @return Anything that implementation decides to return.
      */
-    protected <N extends InnerNode> T doVisitNamedListNode(String key, NamedListNode<N> node) {
+    protected T doVisitNamedListNode(String key, NamedListNode<?> node) {
         for (String namedListKey : node.namedListKeys()) {
             int prevPos = startVisit(namedListKey, true, false);
 
             try {
-                doVisitInnerNode(namedListKey, node.get(namedListKey));
+                doVisitInnerNode(namedListKey, node.getInnerNode(namedListKey));
             }
             finally {
                 endVisit(prevPos);
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/WrongPolymorphicTypeIdException.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/WrongPolymorphicTypeIdException.java
new file mode 100644
index 0000000..82fb074
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/WrongPolymorphicTypeIdException.java
@@ -0,0 +1,33 @@
+/*
+ * 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.util;
+
+/**
+ * Thrown when the wrong (unknown) type of polymorphic configuration is used.
+ */
+public class WrongPolymorphicTypeIdException extends RuntimeException {
+    /**
+     * Constructor.
+     *
+     * @param message Error message.
+     * @param cause Cause of the exception.
+     */
+    public WrongPolymorphicTypeIdException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
index d8b17f2..93679b4 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
@@ -194,6 +194,7 @@ public class ConfigurationChangerTest {
             List.of(KEY),
             Map.of(MaybeInvalid.class, Set.of(validator)),
             storage,
+            List.of(),
             List.of()
         );
 
@@ -481,6 +482,6 @@ public class ConfigurationChangerTest {
 
     /** */
     private ConfigurationChanger createChanger(RootKey<?, ?> rootKey) {
-        return new TestConfigurationChanger(cgen, List.of(rootKey), Map.of(), storage, List.of());
+        return new TestConfigurationChanger(cgen, List.of(rootKey), Map.of(), storage, List.of(), List.of());
     }
 }
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationRegistryTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationRegistryTest.java
index 8b65be0..c14f48e 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationRegistryTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationRegistryTest.java
@@ -19,8 +19,12 @@ package org.apache.ignite.internal.configuration;
 
 import java.util.List;
 import java.util.Map;
+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.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
 import org.junit.jupiter.api.Test;
@@ -41,17 +45,66 @@ public class ConfigurationRegistryTest {
                 List.of(SecondRootConfiguration.KEY),
                 Map.of(),
                 new TestConfigurationStorage(LOCAL),
-                List.of(ExtendedFirstRootConfigurationSchema.class)
+                List.of(ExtendedFirstRootConfigurationSchema.class),
+                List.of()
             )
         );
 
         // Check that everything is fine.
-        new ConfigurationRegistry(
+        ConfigurationRegistry configRegistry = new ConfigurationRegistry(
             List.of(FirstRootConfiguration.KEY, SecondRootConfiguration.KEY),
             Map.of(),
             new TestConfigurationStorage(LOCAL),
-            List.of(ExtendedFirstRootConfigurationSchema.class)
+            List.of(ExtendedFirstRootConfigurationSchema.class),
+            List.of()
         );
+
+        configRegistry.stop();
+    }
+
+    /** */
+    @Test
+    void testValidationPolymorphicConfigurationExtensions() {
+        // There is a polymorphic extension that is missing from the schema.
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> new ConfigurationRegistry(
+                List.of(ThirdRootConfiguration.KEY),
+                Map.of(),
+                new TestConfigurationStorage(LOCAL),
+                List.of(),
+                List.of(Second0PolymorphicConfigurationSchema.class)
+            )
+        );
+
+        // There are two polymorphic extensions with the same id.
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> new ConfigurationRegistry(
+                List.of(ThirdRootConfiguration.KEY),
+                Map.of(),
+                new TestConfigurationStorage(LOCAL),
+                List.of(),
+                List.of(First0PolymorphicConfigurationSchema.class, ErrorFirst0PolymorphicConfigurationSchema.class)
+            )
+        );
+
+        // Check that everything is fine.
+        ConfigurationRegistry configRegistry = new ConfigurationRegistry(
+            List.of(ThirdRootConfiguration.KEY, FourthRootConfiguration.KEY, FifthRootConfiguration.KEY),
+            Map.of(),
+            new TestConfigurationStorage(LOCAL),
+            List.of(),
+            List.of(
+                First0PolymorphicConfigurationSchema.class,
+                First1PolymorphicConfigurationSchema.class,
+                Second0PolymorphicConfigurationSchema.class,
+                Third0PolymorphicConfigurationSchema.class,
+                Third1PolymorphicConfigurationSchema.class
+            )
+        );
+
+        configRegistry.stop();
     }
 
     /**
@@ -83,4 +136,106 @@ public class ConfigurationRegistryTest {
         @Value(hasDefault = true)
         public String strEx = "str";
     }
+
+    /**
+     * Third root configuration.
+     */
+    @ConfigurationRoot(rootName = "third")
+    public static class ThirdRootConfigurationSchema {
+        /** First polymorphic configuration scheme */
+        @ConfigValue
+        public FirstPolymorphicConfigurationSchema polymorphicConfig;
+    }
+
+    /**
+     * Fourth root configuration.
+     */
+    @ConfigurationRoot(rootName = "fourth")
+    public static class FourthRootConfigurationSchema {
+        /** Second polymorphic configuration scheme */
+        @ConfigValue
+        public SecondPolymorphicConfigurationSchema polymorphicConfig;
+    }
+
+    /**
+     * Fifth root configuration.
+     */
+    @ConfigurationRoot(rootName = "fifth")
+    public static class FifthRootConfigurationSchema {
+        /** Third polymorphic configuration scheme */
+        @ConfigValue
+        public ThirdPolymorphicConfigurationSchema polymorphicConfig;
+    }
+
+    /**
+     * Simple first polymorphic configuration scheme.
+     */
+    @PolymorphicConfig
+    public static class FirstPolymorphicConfigurationSchema {
+        /** Polymorphic type id field. */
+        @PolymorphicId
+        public String typeId;
+    }
+
+    /**
+     * First {@link FirstPolymorphicConfigurationSchema} extension.
+     */
+    @PolymorphicConfigInstance("first0")
+    public static class First0PolymorphicConfigurationSchema extends FirstPolymorphicConfigurationSchema {
+    }
+
+    /**
+     * Second {@link FirstPolymorphicConfigurationSchema} extension.
+     */
+    @PolymorphicConfigInstance("first1")
+    public static class First1PolymorphicConfigurationSchema extends FirstPolymorphicConfigurationSchema {
+    }
+
+    /**
+     * First error {@link FirstPolymorphicConfigurationSchema} extension.
+     */
+    @PolymorphicConfigInstance("first0")
+    public static class ErrorFirst0PolymorphicConfigurationSchema extends FirstPolymorphicConfigurationSchema {
+    }
+
+    /**
+     * Second polymorphic configuration scheme.
+     */
+    @PolymorphicConfig
+    public static class SecondPolymorphicConfigurationSchema {
+        /** Polymorphic type id field. */
+        @PolymorphicId
+        public String typeId;
+    }
+
+    /**
+     * First {@link SecondPolymorphicConfigurationSchema} extension.
+     */
+    @PolymorphicConfigInstance("second0")
+    public static class Second0PolymorphicConfigurationSchema extends SecondPolymorphicConfigurationSchema {
+    }
+
+    /**
+     * Third polymorphic configuration scheme.
+     */
+    @PolymorphicConfig
+    public static class ThirdPolymorphicConfigurationSchema {
+        /** Polymorphic type id field. */
+        @PolymorphicId(hasDefault = true)
+        public String typeId = "third0";
+    }
+
+    /**
+     * First {@link ThirdPolymorphicConfigurationSchema} extension.
+     */
+    @PolymorphicConfigInstance("third0")
+    public static class Third0PolymorphicConfigurationSchema extends ThirdPolymorphicConfigurationSchema {
+    }
+
+    /**
+     * First {@link ThirdPolymorphicConfigurationSchema} extension.
+     */
+    @PolymorphicConfigInstance("third1")
+    public static class Third1PolymorphicConfigurationSchema extends ThirdPolymorphicConfigurationSchema {
+    }
 }
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/DirectPropertiesTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/DirectPropertiesTest.java
index 64d2b6c..ad9c82e 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/DirectPropertiesTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/DirectPropertiesTest.java
@@ -108,7 +108,11 @@ public class DirectPropertiesTest {
 
     /** */
     private final ConfigurationRegistry registry = new ConfigurationRegistry(
-        List.of(DirectConfiguration.KEY), Map.of(), new TestConfigurationStorage(LOCAL), List.of()
+        List.of(DirectConfiguration.KEY),
+        Map.of(),
+        new TestConfigurationStorage(LOCAL),
+        List.of(),
+        List.of()
     );
 
     /** */
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
index 4d2de45..250c7fa 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
@@ -25,6 +25,7 @@ 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.annotation.PolymorphicConfigInstance;
 import org.apache.ignite.configuration.validation.Validator;
 import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
@@ -32,6 +33,7 @@ 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;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicSchemaExtensions;
 
 /** Implementation of {@link ConfigurationChanger} to be used in tests. Has no support of listeners. */
 public class TestConfigurationChanger extends ConfigurationChanger {
@@ -47,6 +49,8 @@ public class TestConfigurationChanger extends ConfigurationChanger {
      * @param storage Configuration storage.
      * @param internalSchemaExtensions Internal extensions ({@link InternalConfiguration})
      *      of configuration schemas ({@link ConfigurationRoot} and {@link Config}).
+     * @param polymorphicSchemaExtensions Polymorphic extensions ({@link PolymorphicConfigInstance})
+     *      of configuration schemas.
      * @throws IllegalArgumentException If the configuration type of the root keys is not equal to the storage type.
      */
     public TestConfigurationChanger(
@@ -54,7 +58,8 @@ public class TestConfigurationChanger extends ConfigurationChanger {
         Collection<RootKey<?, ?>> rootKeys,
         Map<Class<? extends Annotation>, Set<Validator<?, ?>>> validators,
         ConfigurationStorage storage,
-        Collection<Class<?>> internalSchemaExtensions
+        Collection<Class<?>> internalSchemaExtensions,
+        Collection<Class<?>> polymorphicSchemaExtensions
     ) {
         super(
             (oldRoot, newRoot, revision) -> completedFuture(null),
@@ -65,9 +70,10 @@ public class TestConfigurationChanger extends ConfigurationChanger {
 
         this.cgen = cgen;
 
-        Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(internalSchemaExtensions);
+        Map<Class<?>, Set<Class<?>>> internalExtensions = internalSchemaExtensions(internalSchemaExtensions);
+        Map<Class<?>, Set<Class<?>>> polymorphicExtensions = polymorphicSchemaExtensions(polymorphicSchemaExtensions);
 
-        rootKeys.forEach(key -> cgen.compileRootSchema(key.schemaClass(), extensions));
+        rootKeys.forEach(key -> cgen.compileRootSchema(key.schemaClass(), internalExtensions, polymorphicExtensions));
     }
 
     /** {@inheritDoc} */
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGeneratorTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGeneratorTest.java
index 72a5c83..22b8a74 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGeneratorTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGeneratorTest.java
@@ -22,11 +22,16 @@ import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import org.apache.ignite.configuration.ConfigurationReadOnlyException;
 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.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.internal.configuration.ConfigurationChanger;
 import org.apache.ignite.internal.configuration.DynamicConfiguration;
@@ -34,13 +39,17 @@ 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.AfterEach;
 import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
 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.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;
@@ -49,30 +58,48 @@ 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;
 
+    /** Configuration changer. */
+    private ConfigurationChanger changer;
+
     /** */
     @BeforeAll
     public static void beforeAll() {
-        Collection<Class<?>> extensions = List.of(
+        generator = new ConfigurationAsmGenerator();
+    }
+
+    /** */
+    @AfterAll
+    public static void afterAll() {
+        generator = null;
+    }
+
+    /** */
+    @BeforeEach
+    void beforeEach() {
+        Collection<Class<?>> internalExtensions = List.of(
             ExtendedTestRootConfigurationSchema.class,
             ExtendedSecondTestRootConfigurationSchema.class,
             ExtendedTestConfigurationSchema.class,
             ExtendedSecondTestConfigurationSchema.class
         );
 
-        generator = new ConfigurationAsmGenerator();
+        Collection<Class<?>> polymorphicExtensions = List.of(
+            FirstPolymorphicInstanceTestConfigurationSchema.class,
+            SecondPolymorphicInstanceTestConfigurationSchema.class,
+            FirstPolymorphicNamedInstanceTestConfigurationSchema.class,
+            SecondPolymorphicNamedInstanceTestConfigurationSchema.class
+        );
 
         changer = new TestConfigurationChanger(
             generator,
             List.of(TestRootConfiguration.KEY),
             Map.of(),
             new TestConfigurationStorage(LOCAL),
-            extensions
+            internalExtensions,
+            polymorphicExtensions
         );
 
         changer.start();
@@ -80,17 +107,14 @@ public class ConfigurationAsmGeneratorTest {
     }
 
     /** */
-    @AfterAll
-    public static void after() {
+    @AfterEach
+    void afterEach() {
         changer.stop();
-
-        generator = null;
-        changer = null;
     }
 
     /** */
     @Test
-    void testExtendedRootConfiguration() throws Exception {
+    void testInternalRootConfiguration() throws Exception {
         DynamicConfiguration<?, ?> config = generator.instantiateCfg(TestRootConfiguration.KEY, changer);
 
         TestRootConfiguration baseRootConfig = (TestRootConfiguration)config;
@@ -134,7 +158,7 @@ public class ConfigurationAsmGeneratorTest {
 
     /** */
     @Test
-    void testExtendedSubConfiguration() throws Exception {
+    void testInternalSubConfiguration() throws Exception {
         DynamicConfiguration<?, ?> config = generator.instantiateCfg(TestRootConfiguration.KEY, changer);
 
         TestRootConfiguration rootConfig = (TestRootConfiguration)config;
@@ -175,7 +199,7 @@ public class ConfigurationAsmGeneratorTest {
 
     /** */
     @Test
-    void testExtendedNamedConfiguration() throws Exception {
+    void testInternalNamedConfiguration() throws Exception {
         DynamicConfiguration<?, ?> config = generator.instantiateCfg(TestRootConfiguration.KEY, changer);
 
         TestRootConfiguration rootConfig = (TestRootConfiguration)config;
@@ -231,6 +255,228 @@ public class ConfigurationAsmGeneratorTest {
         subInnerNode.construct("i1", null, true);
     }
 
+    /** */
+    @Test
+    void testPolymorphicSubConfiguration() throws Exception {
+        TestRootConfiguration rootConfig = (TestRootConfiguration)generator.instantiateCfg(TestRootConfiguration.KEY, changer);
+
+        // Check defaults.
+
+        FirstPolymorphicInstanceTestConfiguration firstCfg = (FirstPolymorphicInstanceTestConfiguration)rootConfig.polymorphicSubCfg();
+        assertEquals("first", firstCfg.typeId().value());
+        assertEquals("strVal", firstCfg.strVal().value());
+        assertEquals(0, firstCfg.intVal().value());
+
+        FirstPolymorphicInstanceTestView firstVal = (FirstPolymorphicInstanceTestView)firstCfg.value();
+        assertEquals("first", firstVal.typeId());
+        assertEquals("strVal", firstVal.strVal());
+        assertEquals(0, firstVal.intVal());
+
+        firstVal = (FirstPolymorphicInstanceTestView)rootConfig.value().polymorphicSubCfg();
+        assertEquals("first", firstVal.typeId());
+        assertEquals("strVal", firstVal.strVal());
+        assertEquals(0, firstVal.intVal());
+
+        // Check simple changes.
+
+        firstCfg.strVal().update("strVal1").get(1, SECONDS);
+        firstCfg.intVal().update(1).get(1, SECONDS);
+
+        assertEquals("first", firstCfg.typeId().value());
+        assertEquals("strVal1", firstCfg.strVal().value());
+        assertEquals(1, firstCfg.intVal().value());
+
+        firstCfg.change(c -> ((FirstPolymorphicInstanceTestChange)c).changeIntVal(2).changeStrVal("strVal2")).get(1, SECONDS);
+
+        assertEquals("first", firstCfg.typeId().value());
+        assertEquals("strVal2", firstCfg.strVal().value());
+        assertEquals(2, firstCfg.intVal().value());
+
+        rootConfig.change(c -> c.changePolymorphicSubCfg(
+            c1 -> ((FirstPolymorphicInstanceTestChange)c1).changeIntVal(3).changeStrVal("strVal3")
+        )).get(1, SECONDS);
+
+        assertEquals("first", firstCfg.typeId().value());
+        assertEquals("strVal3", firstCfg.strVal().value());
+        assertEquals(3, firstCfg.intVal().value());
+
+        // Check convert.
+
+        rootConfig.polymorphicSubCfg().change(c -> c.convert(SecondPolymorphicInstanceTestChange.class)).get(1, SECONDS);
+
+        SecondPolymorphicInstanceTestConfiguration secondCfg = (SecondPolymorphicInstanceTestConfiguration)rootConfig.polymorphicSubCfg();
+        assertEquals("second", secondCfg.typeId().value());
+        assertEquals("strVal3", secondCfg.strVal().value());
+        assertEquals(0, secondCfg.intVal().value());
+        assertEquals(0L, secondCfg.longVal().value());
+
+        SecondPolymorphicInstanceTestView secondView = (SecondPolymorphicInstanceTestView)secondCfg.value();
+        assertEquals("second", secondView.typeId());
+        assertEquals("strVal3", secondView.strVal());
+        assertEquals(0, secondView.intVal());
+        assertEquals(0L, secondView.longVal());
+
+        rootConfig.polymorphicSubCfg().change(c -> c.convert(FirstPolymorphicInstanceTestChange.class)).get(1, SECONDS);
+
+        firstCfg = (FirstPolymorphicInstanceTestConfiguration)rootConfig.polymorphicSubCfg();
+        assertEquals("first", firstCfg.typeId().value());
+        assertEquals("strVal3", firstCfg.strVal().value());
+        assertEquals(0, firstCfg.intVal().value());
+    }
+
+    /** */
+    @Test
+    void testPolymorphicErrors() throws Exception {
+        TestRootConfiguration rootConfig = (TestRootConfiguration)generator.instantiateCfg(TestRootConfiguration.KEY, changer);
+
+        PolymorphicTestConfiguration polymorphicCfg = rootConfig.polymorphicSubCfg();
+
+        // Checks for an error on an attempt to update polymorphicTypeId field.
+        assertThrows(
+            ConfigurationReadOnlyException.class,
+            () -> polymorphicCfg.typeId().update("second").get(1, SECONDS)
+        );
+
+        // Checks for an error on an attempt to read a field of a different type of polymorphic configuration.
+        assertThrows(ExecutionException.class, () -> polymorphicCfg.change(c -> {
+                    FirstPolymorphicInstanceTestView firstView = (FirstPolymorphicInstanceTestView)c;
+
+                    c.convert(SecondPolymorphicInstanceTestChange.class);
+
+                    firstView.intVal();
+                }
+            ).get(1, SECONDS)
+        );
+
+        // Checks for an error on an attempt to change a field of a different type of polymorphic configuration.
+        assertThrows(ExecutionException.class, () -> polymorphicCfg.change(c -> {
+                    FirstPolymorphicInstanceTestChange firstChange = (FirstPolymorphicInstanceTestChange)c;
+
+                    c.convert(SecondPolymorphicInstanceTestChange.class);
+
+                    firstChange.changeIntVal(10);
+                }
+            ).get(1, SECONDS)
+        );
+    }
+
+    /** */
+    @Test
+    void testPolymorphicNamedConfigurationAdd() throws Exception {
+        TestRootConfiguration rootConfig = (TestRootConfiguration)generator.instantiateCfg(TestRootConfiguration.KEY, changer);
+        
+        // Check add named polymorphic config.
+
+        rootConfig.polymorphicNamedCfg()
+            .change(c -> c.create("0", c1 -> c1.convert(FirstPolymorphicNamedInstanceTestChange.class))
+                .create("1", c1 -> c1.convert(SecondPolymorphicNamedInstanceTestChange.class)
+                    .changeIntVal(1)
+                    .changeLongVal(1)
+                    .changeStrVal("strVal1")
+                )
+            ).get(1, SECONDS);
+
+        FirstPolymorphicNamedInstanceTestConfiguration firstCfg =
+            (FirstPolymorphicNamedInstanceTestConfiguration)rootConfig.polymorphicNamedCfg().get("0");
+
+        assertEquals("strVal", firstCfg.strVal().value());
+        assertEquals(0, firstCfg.intVal().value());
+
+        SecondPolymorphicNamedInstanceTestConfiguration secondCfg =
+            (SecondPolymorphicNamedInstanceTestConfiguration)rootConfig.polymorphicNamedCfg().get("1");
+
+        assertEquals("strVal1", secondCfg.strVal().value());
+        assertEquals(1, secondCfg.intVal().value());
+        assertEquals(1L, secondCfg.longVal().value());
+
+        // Check config values.
+        FirstPolymorphicNamedInstanceTestView firstVal = (FirstPolymorphicNamedInstanceTestView)firstCfg.value();
+
+        assertEquals("strVal", firstVal.strVal());
+        assertEquals(0, firstVal.intVal());
+
+        firstVal = (FirstPolymorphicNamedInstanceTestView)rootConfig.polymorphicNamedCfg().value().get("0");
+
+        assertEquals("strVal", firstVal.strVal());
+        assertEquals(0, firstVal.intVal());
+
+        firstVal = (FirstPolymorphicNamedInstanceTestView)rootConfig.value().polymorphicNamedCfg().get("0");
+
+        assertEquals("strVal", firstVal.strVal());
+        assertEquals(0, firstVal.intVal());
+
+        SecondPolymorphicNamedInstanceTestView secondVal = (SecondPolymorphicNamedInstanceTestView)secondCfg.value();
+
+        assertEquals("strVal1", secondVal.strVal());
+        assertEquals(1, secondVal.intVal());
+        assertEquals(1L, secondVal.longVal());
+
+        secondVal = (SecondPolymorphicNamedInstanceTestView)rootConfig.polymorphicNamedCfg().value().get("1");
+
+        assertEquals("strVal1", secondVal.strVal());
+        assertEquals(1, secondVal.intVal());
+        assertEquals(1L, secondVal.longVal());
+
+        secondVal = (SecondPolymorphicNamedInstanceTestView)rootConfig.value().polymorphicNamedCfg().get("1");
+
+        assertEquals("strVal1", secondVal.strVal());
+        assertEquals(1, secondVal.intVal());
+        assertEquals(1L, secondVal.longVal());
+    }
+
+    /** */
+    @Test
+    void testPolymorphicNamedConfigurationChange() throws Exception {
+        TestRootConfiguration rootConfig = (TestRootConfiguration)generator.instantiateCfg(TestRootConfiguration.KEY, changer);
+
+        rootConfig.polymorphicNamedCfg()
+            .change(c -> c.create("0", c1 -> c1.convert(FirstPolymorphicNamedInstanceTestChange.class)))
+            .get(1, SECONDS);
+
+        FirstPolymorphicNamedInstanceTestConfiguration firstCfg =
+            (FirstPolymorphicNamedInstanceTestConfiguration)rootConfig.polymorphicNamedCfg().get("0");
+
+        firstCfg.intVal().update(1).get(1, SECONDS);
+
+        assertEquals(1, firstCfg.intVal().value());
+
+        firstCfg.change(c -> ((FirstPolymorphicNamedInstanceTestChange)c).changeIntVal(2).changeStrVal("strVal2"))
+            .get(1, SECONDS);
+
+        assertEquals(2, firstCfg.intVal().value());
+        assertEquals("strVal2", firstCfg.strVal().value());
+
+        // Check convert.
+
+        rootConfig.polymorphicNamedCfg().get("0")
+            .change(c -> c.convert(SecondPolymorphicNamedInstanceTestChange.class)
+                .changeLongVal(3)
+                .changeIntVal(3)
+                .changeStrVal("strVal3")
+            ).get(1, SECONDS);
+
+        SecondPolymorphicNamedInstanceTestConfiguration secondCfg =
+            (SecondPolymorphicNamedInstanceTestConfiguration)rootConfig.polymorphicNamedCfg().get("0");
+
+        assertEquals(3, secondCfg.intVal().value());
+        assertEquals(3L, secondCfg.longVal().value());
+        assertEquals("strVal3", secondCfg.strVal().value());
+    }
+
+    /** */
+    @Test
+    void testPolymorphicNamedConfigurationRemove() throws Exception {
+        TestRootConfiguration rootConfig = (TestRootConfiguration)generator.instantiateCfg(TestRootConfiguration.KEY, changer);
+
+        rootConfig.polymorphicNamedCfg()
+            .change(c -> c.create("0", c1 -> c1.convert(FirstPolymorphicNamedInstanceTestChange.class)))
+            .get(1, SECONDS);
+
+        rootConfig.polymorphicNamedCfg().change(c -> c.delete("0")).get(1, SECONDS);
+
+        assertNull(rootConfig.polymorphicNamedCfg().get("0"));
+    }
+
     /**
      * Test root configuration schema.
      */
@@ -251,6 +497,14 @@ public class ConfigurationAsmGeneratorTest {
         /** Named configuration field. */
         @NamedConfigValue
         public TestConfigurationSchema namedCfg;
+
+        /** Polymorphic sub configuration field. */
+        @ConfigValue
+        public PolymorphicTestConfigurationSchema polymorphicSubCfg;
+
+        /** Polymorphic named configuration field. */
+        @NamedConfigValue
+        public PolymorphicNamedTestConfigurationSchema polymorphicNamedCfg;
     }
 
     /**
@@ -306,4 +560,80 @@ public class ConfigurationAsmGeneratorTest {
         @Value(hasDefault = true)
         public int i1 = 0;
     }
+
+    /**
+     * Polymorphic configuration scheme.
+     */
+    @PolymorphicConfig
+    public static class PolymorphicTestConfigurationSchema {
+        /** Polymorphic type id field. */
+        @PolymorphicId(hasDefault = true)
+        public String typeId = "first";
+
+        /** String value. */
+        @Value(hasDefault = true)
+        public String strVal = "strVal";
+    }
+
+    /**
+     * First instance of the polymorphic configuration schema.
+     */
+    @PolymorphicConfigInstance("first")
+    public static class FirstPolymorphicInstanceTestConfigurationSchema extends PolymorphicTestConfigurationSchema {
+        /** Integer value. */
+        @Value(hasDefault = true)
+        public int intVal = 0;
+    }
+
+    /**
+     * Second instance of the polymorphic configuration schema.
+     */
+    @PolymorphicConfigInstance("second")
+    public static class SecondPolymorphicInstanceTestConfigurationSchema extends PolymorphicTestConfigurationSchema {
+        /** Integer value. */
+        @Value(hasDefault = true)
+        public int intVal = 0;
+
+        /** Long value. */
+        @Value(hasDefault = true)
+        public long longVal = 0;
+    }
+
+    /**
+     * Polymorphic named configuration scheme.
+     */
+    @PolymorphicConfig
+    public static class PolymorphicNamedTestConfigurationSchema {
+        /** Polymorphic type id field. */
+        @PolymorphicId(hasDefault = true)
+        public String typeId;
+
+        /** String value. */
+        @Value(hasDefault = true)
+        public String strVal = "strVal";
+    }
+
+    /**
+     * First instance of the named polymorphic configuration schema.
+     */
+    @PolymorphicConfigInstance("first")
+    public static class FirstPolymorphicNamedInstanceTestConfigurationSchema extends PolymorphicNamedTestConfigurationSchema {
+        /** Integer value. */
+        @Value(hasDefault = true)
+        public int intVal = 0;
+    }
+
+    /**
+     * Second instance of the named polymorphic configuration schema.
+     */
+    @PolymorphicConfigInstance("second")
+    public static class SecondPolymorphicNamedInstanceTestConfigurationSchema extends PolymorphicNamedTestConfigurationSchema {
+        /** Integer value. */
+        @Value(hasDefault = true)
+        public int intVal = 0;
+
+        /** Long value. */
+        @Value(hasDefault = true)
+        public long longVal = 0;
+    }
 }
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
index e43232a..ed73efa 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
@@ -29,6 +29,9 @@ import com.typesafe.config.ConfigValue;
 import org.apache.ignite.configuration.annotation.Config;
 import org.apache.ignite.configuration.annotation.ConfigurationRoot;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.internal.configuration.ConfigurationRegistry;
 import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
@@ -45,6 +48,7 @@ import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
@@ -62,6 +66,10 @@ public class HoconConverterTest {
         /** */
         @NamedConfigValue(syntheticKeyName = "p")
         public HoconPrimitivesConfigurationSchema primitivesList;
+
+        /** Polymorphic config. */
+        @org.apache.ignite.configuration.annotation.ConfigValue
+        public HoconPolymorphicConfigurationSchema polymorphicCfg;
     }
 
     /**
@@ -148,6 +156,36 @@ public class HoconConverterTest {
         public String stringVal = "";
     }
 
+    /**
+     * Configuration schema for testing the support of polymorphic configuration.
+     */
+    @PolymorphicConfig
+    public static class HoconPolymorphicConfigurationSchema {
+        /** Polymorphic type id field. */
+        @PolymorphicId(hasDefault = true)
+        public String typeId = "first";
+    }
+
+    /**
+     * Configuration schema for testing the support of polymorphic configuration.
+     */
+    @PolymorphicConfigInstance("first")
+    public static class HoconFirstPolymorphicInstanceConfigurationSchema extends HoconPolymorphicConfigurationSchema {
+        /** Long value. */
+        @Value(hasDefault = true)
+        public int longVal = 0;
+    }
+
+    /**
+     * Configuration schema for testing the support of polymorphic configuration.
+     */
+    @PolymorphicConfigInstance("second")
+    public static class HoconSecondPolymorphicInstanceConfigurationSchema extends HoconPolymorphicConfigurationSchema {
+        /** Integer value. */
+        @Value(hasDefault = true)
+        public int intVal = 0;
+    }
+
     /** */
     private static ConfigurationRegistry registry;
 
@@ -161,7 +199,11 @@ public class HoconConverterTest {
             List.of(HoconRootConfiguration.KEY),
             Map.of(),
             new TestConfigurationStorage(LOCAL),
-            List.of()
+            List.of(),
+            List.of(
+                HoconFirstPolymorphicInstanceConfigurationSchema.class,
+                HoconSecondPolymorphicInstanceConfigurationSchema.class
+            )
         );
 
         registry.start();
@@ -185,15 +227,16 @@ public class HoconConverterTest {
         configuration.change(cfg -> cfg
             .changePrimitivesList(list -> list.namedListKeys().forEach(list::delete))
             .changeArraysList(list -> list.namedListKeys().forEach(list::delete))
+            .changePolymorphicCfg(c -> {})
         ).get(1, SECONDS);
     }
 
     /** */
     @Test
     public void toHoconBasic() {
-        assertEquals("root{arraysList=[],primitivesList=[]}", asHoconStr(List.of()));
+        assertEquals("root{arraysList=[],polymorphicCfg{longVal=0,typeId=first},primitivesList=[]}", asHoconStr(List.of()));
 
-        assertEquals("arraysList=[],primitivesList=[]", asHoconStr(List.of("root")));
+        assertEquals("arraysList=[],polymorphicCfg{longVal=0,typeId=first},primitivesList=[]", asHoconStr(List.of("root")));
 
         assertEquals("[]", asHoconStr(List.of("root", "arraysList")));
 
@@ -549,6 +592,35 @@ public class HoconConverterTest {
         );
     }
 
+    /** */
+    @Test
+    void testPolymorphicConfig() throws Throwable {
+        // Check defaults.
+        assertEquals("root{arraysList=[],polymorphicCfg{longVal=0,typeId=first},primitivesList=[]}", asHoconStr(List.of()));
+
+        // Check change type.
+        change("root.polymorphicCfg.typeId = second");
+
+        assertInstanceOf(HoconSecondPolymorphicInstanceConfiguration.class, configuration.polymorphicCfg());
+        assertEquals("root{arraysList=[],polymorphicCfg{intVal=0,typeId=second},primitivesList=[]}", asHoconStr(List.of()));
+
+        // Check change field.
+        change("root.polymorphicCfg.intVal = 10");
+        assertEquals("root{arraysList=[],polymorphicCfg{intVal=10,typeId=second},primitivesList=[]}", asHoconStr(List.of()));
+
+        // Check error: unknown typeId.
+        assertThrowsIllegalArgException(
+            () -> change("root.polymorphicCfg.typeId = ERROR"),
+            "Polymorphic configuration type is not correct: ERROR"
+        );
+
+        // Check error: try update field from typeId = first.
+        assertThrowsIllegalArgException(
+            () -> change("root.polymorphicCfg.longVal = 10"),
+            "'root.polymorphicCfg' configuration doesn't have the 'longVal' sub-configuration"
+        );
+    }
+
     /**
      * Updates the configuration using the provided HOCON string.
      */
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationAnyListenerTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationAnyListenerTest.java
index 6648f31..9f77c42 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationAnyListenerTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationAnyListenerTest.java
@@ -113,6 +113,7 @@ public class ConfigurationAnyListenerTest {
             List.of(RootConfiguration.KEY),
             Map.of(),
             new TestConfigurationStorage(LOCAL),
+            List.of(),
             List.of()
         );
 
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
index de630d0..ca8b18d 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
@@ -91,6 +91,7 @@ public class ConfigurationListenerTest {
             List.of(ParentConfiguration.KEY),
             Map.of(),
             testConfigurationStorage,
+            List.of(),
             List.of()
         );
 
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java
index c747ee5..0d9cabe 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java
@@ -53,6 +53,7 @@ public class UsageTest {
             List.of(LocalConfiguration.KEY),
             Map.of(),
             new TestConfigurationStorage(LOCAL),
+            List.of(),
             List.of()
         );
 
@@ -114,6 +115,7 @@ public class UsageTest {
             List.of(NetworkConfiguration.KEY, LocalConfiguration.KEY),
             Map.of(),
             new TestConfigurationStorage(LOCAL),
+            List.of(),
             List.of()
         );
 
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/ConfigurationExtension.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/ConfigurationExtension.java
index c2d97b5..b6dbd91 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/ConfigurationExtension.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/ConfigurationExtension.java
@@ -54,6 +54,7 @@ import org.junit.platform.commons.support.HierarchyTraversalMode;
 import static java.util.concurrent.Executors.newSingleThreadExecutor;
 import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
 import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.internalSchemaExtensions;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicSchemaExtensions;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -161,7 +162,11 @@ public class ConfigurationExtension implements BeforeEachCallback, AfterEachCall
         // classes, extension is designed to mock actual configurations from public API to configure Ignite components.
         Class<?> schemaClass = Class.forName(type.getCanonicalName() + "Schema");
 
-        cgen.compileRootSchema(schemaClass, internalSchemaExtensions(List.of(annotation.extensions())));
+        cgen.compileRootSchema(
+            schemaClass,
+            internalSchemaExtensions(List.of(annotation.internalExtensions())),
+            polymorphicSchemaExtensions(List.of(annotation.polymorphicExtensions()))
+        );
 
         // RootKey must be mocked, there's no way to instantiate it using a public constructor.
         RootKey rootKey = mock(RootKey.class);
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/ConfigurationExtensionTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/ConfigurationExtensionTest.java
index b29c68a..21236ee 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/ConfigurationExtensionTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/ConfigurationExtensionTest.java
@@ -94,7 +94,7 @@ class ConfigurationExtensionTest {
     /** Tests that internal configuration extensions work properly on injected configuration instance. */
     @Test
     public void internalConfiguration(
-        @InjectConfiguration(extensions = {ExtendedConfigurationSchema.class}) BasicConfiguration cfg
+        @InjectConfiguration(internalExtensions = {ExtendedConfigurationSchema.class}) BasicConfiguration cfg
     ) throws Exception {
         assertThat(cfg, is(instanceOf(ExtendedConfiguration.class)));
 
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/InjectConfiguration.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/InjectConfiguration.java
index e5e5a73..cd3e285 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/InjectConfiguration.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/InjectConfiguration.java
@@ -22,6 +22,8 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
 import org.apache.ignite.internal.configuration.ConfigurationChanger;
 import org.apache.ignite.internal.configuration.ConfigurationRegistry;
 import org.intellij.lang.annotations.Language;
@@ -64,5 +66,13 @@ public @interface InjectConfiguration {
      *
      * @return Array of configuration schema extensions.
      */
-    Class<?>[] extensions() default {};
+    Class<?>[] internalExtensions() default {};
+
+    /**
+     * Array of configuration schema extensions. Every class in the array must be annotated with
+     * {@link PolymorphicConfigInstance} and extend some {@link PolymorphicConfig} schema.
+     *
+     * @return Array of configuration schema extensions.
+     */
+    Class<?>[] polymorphicExtensions() default {};
 }
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConfigurationArrayTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConfigurationArrayTest.java
index 927e047..cd497a0 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConfigurationArrayTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConfigurationArrayTest.java
@@ -85,7 +85,7 @@ public class ConfigurationArrayTest {
     /** */
     @BeforeAll
     public static void beforeAll() {
-        cgen.compileRootSchema(TestArrayConfigurationSchema.class, Map.of());
+        cgen.compileRootSchema(TestArrayConfigurationSchema.class, Map.of(), Map.of());
     }
 
     /** */
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java
index 4d72612..fcf60d1 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java
@@ -39,7 +39,7 @@ public class ConstructableTreeNodeTest {
     public static void beforeAll() {
         cgen = new ConfigurationAsmGenerator();
 
-        cgen.compileRootSchema(TraversableTreeNodeTest.ParentConfigurationSchema.class, Map.of());
+        cgen.compileRootSchema(TraversableTreeNodeTest.ParentConfigurationSchema.class, Map.of(), Map.of());
     }
 
     @AfterAll
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
index a34d86e..2a70166 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
@@ -99,7 +99,15 @@ public class NamedListNodeTest {
     public void before() {
         storage = new TestConfigurationStorage(LOCAL);
 
-        changer = new TestConfigurationChanger(cgen, List.of(AConfiguration.KEY), Map.of(), storage, List.of());
+        changer = new TestConfigurationChanger(
+            cgen,
+            List.of(AConfiguration.KEY),
+            Map.of(),
+            storage,
+            List.of(),
+            List.of()
+        );
+
         changer.start();
     }
 
@@ -271,7 +279,7 @@ public class NamedListNodeTest {
     /** Tests exceptions described in methods signatures. */
     @Test
     public void errors() throws Exception {
-        var b = new NamedListNode<>("name", () -> cgen.instantiateNode(BConfigurationSchema.class));
+        var b = new NamedListNode<>("name", () -> cgen.instantiateNode(BConfigurationSchema.class), null);
 
         b.create("X", x -> {}).create("Y", y -> {});
 
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/TraversableTreeNodeTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/TraversableTreeNodeTest.java
index 95a8d7e..8893fdc 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/TraversableTreeNodeTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/TraversableTreeNodeTest.java
@@ -54,7 +54,7 @@ public class TraversableTreeNodeTest {
     public static void beforeAll() {
         cgen = new ConfigurationAsmGenerator();
 
-        cgen.compileRootSchema(ParentConfigurationSchema.class, Map.of());
+        cgen.compileRootSchema(ParentConfigurationSchema.class, Map.of(), Map.of());
     }
 
     @AfterAll
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
index cc7cd68..2803e11 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
@@ -18,19 +18,22 @@
 package org.apache.ignite.internal.configuration.util;
 
 import java.io.Serializable;
-import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.HashMap;
 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.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
 import org.apache.ignite.configuration.annotation.Value;
 import org.apache.ignite.internal.configuration.RootInnerNode;
 import org.apache.ignite.internal.configuration.SuperRoot;
@@ -38,6 +41,7 @@ import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
 import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
 import org.apache.ignite.internal.configuration.tree.ConverterToMapVisitor;
 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 org.jetbrains.annotations.NotNull;
 import org.junit.jupiter.api.AfterAll;
@@ -45,7 +49,7 @@ 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 java.util.stream.Collectors.toList;
 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;
@@ -55,9 +59,11 @@ import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.EM
 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.compressDeletedEntries;
 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.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicSchemaExtensions;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.aMapWithSize;
 import static org.hamcrest.Matchers.allOf;
@@ -82,7 +88,16 @@ public class ConfigurationUtilTest {
     public static void beforeAll() {
         cgen = new ConfigurationAsmGenerator();
 
-        cgen.compileRootSchema(ParentConfigurationSchema.class, Map.of());
+        cgen.compileRootSchema(ParentConfigurationSchema.class, Map.of(), Map.of());
+
+        cgen.compileRootSchema(
+            PolymorphicRootConfigurationSchema.class,
+            Map.of(),
+            Map.of(
+                PolymorphicConfigurationSchema.class,
+                Set.of(FirstPolymorphicInstanceConfigurationSchema.class, SecondPolymorphicInstanceConfigurationSchema.class)
+            )
+        );
     }
 
     @AfterAll
@@ -90,8 +105,15 @@ public class ConfigurationUtilTest {
         cgen = null;
     }
 
-    public static <P extends InnerNode & ParentChange> P newParentInstance() {
-        return (P)cgen.instantiateNode(ParentConfigurationSchema.class);
+    /**
+     * Creates new instance of {@code *Node} class corresponding to the given configuration Schema.
+     *
+     * @param schemaClass Configuration schema class.
+     * @param <P> Type of {@link InnerNode}.
+     * @return New instance of {@link InnerNode}.
+     */
+    public static <P extends InnerNode> P newNodeInstance(Class<?> schemaClass) {
+        return (P)cgen.instantiateNode(schemaClass);
     }
 
     /** */
@@ -160,9 +182,11 @@ public class ConfigurationUtilTest {
      */
     @Test
     public void findSuccessfully() {
-        var parent = newParentInstance();
+        InnerNode parentNode = newNodeInstance(ParentConfigurationSchema.class);
+
+        ParentChange parentChange = (ParentChange)parentNode;
 
-        parent.changeElements(elements ->
+        parentChange.changeElements(elements ->
             elements.createOrUpdate("name", element ->
                 element.changeChild(child ->
                     child.changeStr("value")
@@ -171,28 +195,28 @@ public class ConfigurationUtilTest {
         );
 
         assertSame(
-            parent,
-            ConfigurationUtil.find(List.of(), parent, true)
+            parentNode,
+            ConfigurationUtil.find(List.of(), parentNode, true)
         );
 
         assertSame(
-            parent.elements(),
-            ConfigurationUtil.find(List.of("elements"), parent, true)
+            parentChange.elements(),
+            ConfigurationUtil.find(List.of("elements"), parentNode, true)
         );
 
         assertSame(
-            parent.elements().get("name"),
-            ConfigurationUtil.find(List.of("elements", "name"), parent, true)
+            parentChange.elements().get("name"),
+            ConfigurationUtil.find(List.of("elements", "name"), parentNode, true)
         );
 
         assertSame(
-            parent.elements().get("name").child(),
-            ConfigurationUtil.find(List.of("elements", "name", "child"), parent, true)
+            parentChange.elements().get("name").child(),
+            ConfigurationUtil.find(List.of("elements", "name", "child"), parentNode, true)
         );
 
         assertSame(
-            parent.elements().get("name").child().str(),
-            ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parent, true)
+            parentChange.elements().get("name").child().str(),
+            ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parentNode, true)
         );
     }
 
@@ -202,13 +226,15 @@ public class ConfigurationUtilTest {
      */
     @Test
     public void findNulls() {
-        var parent = newParentInstance();
+        InnerNode parentNode = newNodeInstance(ParentConfigurationSchema.class);
 
-        assertNull(ConfigurationUtil.find(List.of("elements", "name"), parent, true));
+        ParentChange parentChange = (ParentChange)parentNode;
 
-        parent.changeElements(elements -> elements.createOrUpdate("name", element -> {}));
+        assertNull(ConfigurationUtil.find(List.of("elements", "name"), parentNode, true));
 
-        assertNull(ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parent, true));
+        parentChange.changeElements(elements -> elements.createOrUpdate("name", element -> {}));
+
+        assertNull(ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parentNode, true));
     }
 
     /**
@@ -217,25 +243,27 @@ public class ConfigurationUtilTest {
      */
     @Test
     public void findUnsuccessfully() {
-        var parent = newParentInstance();
+        InnerNode parentNode = newNodeInstance(ParentConfigurationSchema.class);
+
+        ParentChange parentChange = (ParentChange)parentNode;
 
         assertThrows(
             KeyNotFoundException.class,
-            () -> ConfigurationUtil.find(List.of("elements", "name", "child"), parent, true)
+            () -> ConfigurationUtil.find(List.of("elements", "name", "child"), parentNode, true)
         );
 
-        parent.changeElements(elements -> elements.createOrUpdate("name", element -> {}));
+        parentChange.changeElements(elements -> elements.createOrUpdate("name", element -> {}));
 
         assertThrows(
             KeyNotFoundException.class,
-            () -> ConfigurationUtil.find(List.of("elements", "name", "child", "str0"), parent, true)
+            () -> ConfigurationUtil.find(List.of("elements", "name", "child", "str0"), parentNode, true)
         );
 
-        ((NamedElementChange)parent.elements().get("name")).changeChild(child -> child.changeStr("value"));
+        ((NamedElementChange)parentChange.elements().get("name")).changeChild(child -> child.changeStr("value"));
 
         assertThrows(
             KeyNotFoundException.class,
-            () -> ConfigurationUtil.find(List.of("elements", "name", "child", "str", "foo"), parent, true)
+            () -> ConfigurationUtil.find(List.of("elements", "name", "child", "str", "foo"), parentNode, true)
         );
     }
 
@@ -270,7 +298,9 @@ public class ConfigurationUtilTest {
      */
     @Test
     public void fillFromPrefixMapSuccessfully() {
-        var parentNode = newParentInstance();
+        InnerNode parentNode = newNodeInstance(ParentConfigurationSchema.class);
+
+        ParentChange parentChange = (ParentChange)parentNode;
 
         ConfigurationUtil.fillFromPrefixMap(parentNode, Map.of(
             "elements", Map.of(
@@ -287,8 +317,8 @@ public class ConfigurationUtilTest {
             )
         ));
 
-        assertEquals("value1", parentNode.elements().get("name1").child().str());
-        assertEquals("value2", parentNode.elements().get("name2").child().str());
+        assertEquals("value1", parentChange.elements().get("name1").child().str());
+        assertEquals("value2", parentChange.elements().get("name2").child().str());
     }
 
     /**
@@ -296,9 +326,11 @@ public class ConfigurationUtilTest {
      */
     @Test
     public void fillFromPrefixMapSuccessfullyWithRemove() {
-        var parentNode = newParentInstance();
+        InnerNode parentNode = newNodeInstance(ParentConfigurationSchema.class);
+
+        ParentChange parentChange = (ParentChange)parentNode;
 
-        parentNode.changeElements(elements ->
+        parentChange.changeElements(elements ->
             elements.createOrUpdate("name", element ->
                 element.changeChild(child -> {})
             )
@@ -308,7 +340,7 @@ public class ConfigurationUtilTest {
             "elements", singletonMap("name", null)
         ));
 
-        assertNull(parentNode.elements().get("node"));
+        assertNull(parentChange.elements().get("node"));
     }
 
     /**
@@ -316,12 +348,15 @@ public class ConfigurationUtilTest {
      */
     @Test
     public void flattenedUpdatesMap() {
-        var superRoot = new SuperRoot(key -> null, Map.of(ParentConfiguration.KEY, newParentInstance()));
+        var superRoot = new SuperRoot(
+            key -> null,
+            Map.of(ParentConfiguration.KEY, newNodeInstance(ParentConfigurationSchema.class))
+        );
 
-        assertThat(flattenedMap(superRoot, parent -> {}), is(anEmptyMap()));
+        assertThat(flattenedMap(superRoot, ParentConfiguration.KEY, node -> {}), is(anEmptyMap()));
 
         assertThat(
-            flattenedMap(superRoot, parent -> parent
+            flattenedMap(superRoot, ParentConfiguration.KEY, node -> ((ParentChange)node)
                 .changeElements(elements -> elements
                     .create("name", element -> element
                         .changeChild(child -> child.changeStr("foo"))
@@ -337,14 +372,14 @@ public class ConfigurationUtilTest {
         );
 
         assertThat(
-            flattenedMap(superRoot, parent -> parent
+            flattenedMap(superRoot, ParentConfiguration.KEY, node -> ((ParentChange)node)
                 .changeElements(elements1 -> elements1.delete("void"))
             ),
             is(anEmptyMap())
         );
 
         assertThat(
-            flattenedMap(superRoot, parent -> parent
+            flattenedMap(superRoot, ParentConfiguration.KEY, node -> ((ParentChange)node)
                 .changeElements(elements -> elements.delete("name"))
             ),
             is(allOf(
@@ -415,81 +450,88 @@ public class ConfigurationUtilTest {
 
         assertThrows(
             IllegalArgumentException.class,
-            () -> internalSchemaExtensions(List.of(SimpleRootConfigurationSchema.class))
+            () -> internalSchemaExtensions(List.of(InternalRootConfigurationSchema.class))
         );
 
         assertThrows(
             IllegalArgumentException.class,
-            () -> internalSchemaExtensions(List.of(SimpleConfigurationSchema.class))
+            () -> internalSchemaExtensions(List.of(InternalConfigurationSchema.class))
         );
 
         Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(List.of(
-            InternalFirstSimpleRootConfigurationSchema.class,
-            InternalSecondSimpleRootConfigurationSchema.class,
-            InternalFirstSimpleConfigurationSchema.class,
-            InternalSecondSimpleConfigurationSchema.class
+            InternalFirstRootConfigurationSchema.class,
+            InternalSecondRootConfigurationSchema.class,
+            InternalFirstConfigurationSchema.class,
+            InternalSecondConfigurationSchema.class
         ));
 
         assertEquals(2, extensions.size());
 
         assertEquals(
             Set.of(
-                InternalFirstSimpleRootConfigurationSchema.class,
-                InternalSecondSimpleRootConfigurationSchema.class
+                InternalFirstRootConfigurationSchema.class,
+                InternalSecondRootConfigurationSchema.class
             ),
-            extensions.get(SimpleRootConfigurationSchema.class)
+            extensions.get(InternalRootConfigurationSchema.class)
         );
 
         assertEquals(
             Set.of(
-                InternalFirstSimpleConfigurationSchema.class,
-                InternalSecondSimpleConfigurationSchema.class
+                InternalFirstConfigurationSchema.class,
+                InternalSecondConfigurationSchema.class
             ),
-            extensions.get(SimpleConfigurationSchema.class)
+            extensions.get(InternalConfigurationSchema.class)
         );
     }
 
     /** */
     @Test
     void testSchemaFields() {
-        assertThrows(
-            IllegalArgumentException.class,
-            () -> extensionsFields(
-                List.of(
-                    InternalExtendedSimpleRootConfigurationSchema.class,
-                    ErrorInternalExtendedSimpleRootConfigurationSchema.class
-                )
-            )
+        assertTrue(extensionsFields(List.of(), true).isEmpty());
+        assertTrue(extensionsFields(List.of(), false).isEmpty());
+
+        List<Class<?>> extensions0 = List.of(
+            InternalExtendedRootConfigurationSchema.class,
+            ErrorInternalExtendedRootConfigurationSchema.class
         );
 
-        assertTrue(extensionsFields(List.of()).isEmpty());
+        assertThrows(IllegalArgumentException.class, () -> extensionsFields(extensions0, true));
 
-        List<Class<?>> extensions = List.of(
-            InternalFirstSimpleRootConfigurationSchema.class,
-            InternalSecondSimpleRootConfigurationSchema.class
+        assertEquals(
+            extensions0.stream().flatMap(cls -> Arrays.stream(cls.getDeclaredFields())).collect(toList()),
+            List.copyOf(extensionsFields(extensions0, false))
         );
 
-        Set<Field> exp = extensions.stream()
-            .flatMap(cls -> Stream.of(cls.getDeclaredFields()))
-            .collect(toSet());
+        List<Class<?>> extensions1 = List.of(
+            InternalFirstRootConfigurationSchema.class,
+            InternalSecondRootConfigurationSchema.class
+        );
+
+        assertEquals(
+            extensions1.stream().flatMap(cls -> Arrays.stream(cls.getDeclaredFields())).collect(toList()),
+            List.copyOf(extensionsFields(extensions1, true))
+        );
 
-        assertEquals(exp, extensionsFields(extensions));
+        assertEquals(
+            extensions1.stream().flatMap(cls -> Arrays.stream(cls.getDeclaredFields())).collect(toList()),
+            List.copyOf(extensionsFields(extensions1, false))
+        );
     }
 
     /** */
     @Test
     void testFindInternalConfigs() {
-        Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(List.of(
-            InternalFirstSimpleRootConfigurationSchema.class,
-            InternalSecondSimpleRootConfigurationSchema.class,
-            InternalFirstSimpleConfigurationSchema.class,
-            InternalSecondSimpleConfigurationSchema.class
+        Map<Class<?>, Set<Class<?>>> internalExtensions = internalSchemaExtensions(List.of(
+            InternalFirstRootConfigurationSchema.class,
+            InternalSecondRootConfigurationSchema.class,
+            InternalFirstConfigurationSchema.class,
+            InternalSecondConfigurationSchema.class
         ));
 
         ConfigurationAsmGenerator generator = new ConfigurationAsmGenerator();
-        generator.compileRootSchema(SimpleRootConfigurationSchema.class, extensions);
+        generator.compileRootSchema(InternalRootConfigurationSchema.class, internalExtensions, Map.of());
 
-        InnerNode innerNode = generator.instantiateNode(SimpleRootConfigurationSchema.class);
+        InnerNode innerNode = generator.instantiateNode(InternalRootConfigurationSchema.class);
 
         addDefaults(innerNode);
 
@@ -515,17 +557,17 @@ public class ConfigurationUtilTest {
     /** */
     @Test
     void testGetInternalConfigs() {
-        Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(List.of(
-            InternalFirstSimpleRootConfigurationSchema.class,
-            InternalSecondSimpleRootConfigurationSchema.class,
-            InternalFirstSimpleConfigurationSchema.class,
-            InternalSecondSimpleConfigurationSchema.class
+        Map<Class<?>, Set<Class<?>>> internalExtensions = internalSchemaExtensions(List.of(
+            InternalFirstRootConfigurationSchema.class,
+            InternalSecondRootConfigurationSchema.class,
+            InternalFirstConfigurationSchema.class,
+            InternalSecondConfigurationSchema.class
         ));
 
         ConfigurationAsmGenerator generator = new ConfigurationAsmGenerator();
-        generator.compileRootSchema(SimpleRootConfigurationSchema.class, extensions);
+        generator.compileRootSchema(InternalRootConfigurationSchema.class, internalExtensions, Map.of());
 
-        InnerNode innerNode = generator.instantiateNode(SimpleRootConfigurationSchema.class);
+        InnerNode innerNode = generator.instantiateNode(InternalRootConfigurationSchema.class);
 
         addDefaults(innerNode);
 
@@ -580,7 +622,7 @@ public class ConfigurationUtilTest {
         Class<?> schemaClass = InternalWithoutSuperclassConfigurationSchema.class;
         RootKey<?, ?> schemaKey = InternalWithoutSuperclassConfiguration.KEY;
 
-        generator.compileRootSchema(schemaClass, Map.of());
+        generator.compileRootSchema(schemaClass, Map.of(), Map.of());
 
         SuperRoot superRoot = new SuperRoot(
             s -> new RootInnerNode(schemaKey, generator.instantiateNode(schemaClass))
@@ -614,34 +656,168 @@ public class ConfigurationUtilTest {
 
         assertThrows(IllegalArgumentException.class, () -> collectSchemas(List.of(Object.class)));
 
+        Set<Class<?>> schemas = Set.of(
+            LocalFirstConfigurationSchema.class,
+            InternalConfigurationSchema.class,
+            PolymorphicConfigurationSchema.class
+        );
+
+        assertEquals(schemas, collectSchemas(schemas));
+
         assertEquals(
-            Set.of(LocalFirstConfigurationSchema.class, SimpleConfigurationSchema.class),
-            collectSchemas(List.of(LocalFirstConfigurationSchema.class, SimpleConfigurationSchema.class))
+            Set.of(
+                InternalRootConfigurationSchema.class,
+                PolymorphicRootConfigurationSchema.class,
+                InternalConfigurationSchema.class,
+                PolymorphicConfigurationSchema.class
+            ),
+            collectSchemas(List.of(InternalRootConfigurationSchema.class, PolymorphicRootConfigurationSchema.class))
+        );
+    }
+
+    /** */
+    @Test
+    void testPolymorphicSchemaExtensions() {
+        assertTrue(polymorphicSchemaExtensions(List.of()).isEmpty());
+
+        assertThrows(IllegalArgumentException.class, () -> polymorphicSchemaExtensions(List.of(Object.class)));
+
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> polymorphicSchemaExtensions(List.of(LocalFirstConfigurationSchema.class))
+        );
+
+        Set<Class<?>> extensions = Set.of(
+            FirstPolymorphicInstanceConfigurationSchema.class,
+            SecondPolymorphicInstanceConfigurationSchema.class
         );
 
         assertEquals(
-            Set.of(SimpleRootConfigurationSchema.class, SimpleConfigurationSchema.class),
-            collectSchemas(List.of(SimpleRootConfigurationSchema.class))
+            Map.of(PolymorphicConfigurationSchema.class, extensions),
+            polymorphicSchemaExtensions(extensions)
         );
     }
 
+    /** */
+    @Test
+    void testCompressDeletedEntries() {
+        Map<String, String> containsNullLeaf = new HashMap<>();
+
+        containsNullLeaf.put("first", "1");
+        containsNullLeaf.put("second", null);
+
+        Map<String, String> deletedNamedListElement = new HashMap<>();
+
+        deletedNamedListElement.put("third", null);
+        deletedNamedListElement.put(NAME, null);
+
+        Map<String, Object> regular = new HashMap<>();
+
+        regular.put("strVal", "foo");
+        regular.put("intVal", 10);
+
+        Map<String, Object> prefixMap = new HashMap<>();
+
+        prefixMap.put("0", containsNullLeaf);
+        prefixMap.put("1", deletedNamedListElement);
+        prefixMap.put("2", regular);
+
+        Map<String, Object> exp = new HashMap<>();
+
+        exp.put("0", Map.of("first", "1"));
+        exp.put("1", null);
+        exp.put("2", Map.of("strVal", "foo", "intVal", 10));
+
+        compressDeletedEntries(prefixMap);
+
+        assertEquals(exp, prefixMap);
+    }
+
+    /** */
+    @Test
+    void testFlattenedMapPolymorphicConfig() {
+        InnerNode polymorphicRootInnerNode = newNodeInstance(PolymorphicRootConfigurationSchema.class);
+
+        addDefaults(polymorphicRootInnerNode);
+
+        RootKey<?, ?> rootKey = PolymorphicRootConfiguration.KEY;
+
+        SuperRoot superRoot = new SuperRoot(key -> null, Map.of(rootKey, polymorphicRootInnerNode));
+
+        Map<String, Serializable> act = flattenedMap(
+            superRoot,
+            rootKey,
+            node -> ((PolymorphicRootChange)node).changePolymorphicSubCfg(c -> c.convert(SecondPolymorphicInstanceChange.class))
+        );
+
+        Map<String, Serializable> exp = new HashMap<>();
+
+        exp.put("rootPolymorphic.polymorphicSubCfg.typeId", "second");
+        exp.put("rootPolymorphic.polymorphicSubCfg.longVal", 0L);
+
+        exp.put("rootPolymorphic.polymorphicSubCfg.strVal", null);
+        exp.put("rootPolymorphic.polymorphicSubCfg.intVal", 0);
+
+        assertEquals(exp, act);
+    }
+
+    /** */
+    @Test
+    void testFlattenedMapPolymorphicNamedConfig() {
+        InnerNode polymorphicRootInnerNode = newNodeInstance(PolymorphicRootConfigurationSchema.class);
+
+        PolymorphicRootChange polymorphicRootChange = ((PolymorphicRootChange)polymorphicRootInnerNode);
+
+        polymorphicRootChange.changePolymorphicNamedCfg(c -> c.create("0", c1 -> {}));
+
+        addDefaults(polymorphicRootInnerNode);
+
+        RootKey<?, ?> rootKey = PolymorphicRootConfiguration.KEY;
+
+        SuperRoot superRoot = new SuperRoot(key -> null, Map.of(rootKey, polymorphicRootInnerNode));
+
+        Map<String, Serializable> act = flattenedMap(
+            superRoot,
+            rootKey,
+            node -> ((PolymorphicRootChange)node).changePolymorphicNamedCfg(c ->
+                c.createOrUpdate("0", c1 -> c1.convert(SecondPolymorphicInstanceChange.class)))
+        );
+
+        NamedListNode<?> polymorphicNamedCfgListNode = (NamedListNode<?>)polymorphicRootChange.polymorphicNamedCfg();
+        String internalId = polymorphicNamedCfgListNode.internalId("0");
+
+        Map<String, Serializable> exp = new HashMap<>();
+
+        exp.put("rootPolymorphic.polymorphicNamedCfg." + internalId + ".typeId", "second");
+        exp.put("rootPolymorphic.polymorphicNamedCfg." + internalId + ".longVal", 0L);
+
+        exp.put("rootPolymorphic.polymorphicNamedCfg." + internalId + ".strVal", null);
+        exp.put("rootPolymorphic.polymorphicNamedCfg." + internalId + ".intVal", 0);
+
+        assertEquals(exp, act);
+    }
+
     /**
      * Patches super root and returns flat representation of the changes. Passed {@code superRoot} object will contain
      * patched tree when method execution is completed.
      *
      * @param superRoot Super root to patch.
-     * @param patch Closure to cnahge parent node.
+     * @param patch Closure to change inner node.
      * @return Flat map with all changes from the patch.
      */
-    @NotNull private Map<String, Serializable> flattenedMap(SuperRoot superRoot, Consumer<ParentChange> patch) {
+    @NotNull private Map<String, Serializable> flattenedMap(
+        SuperRoot superRoot,
+        RootKey<?, ?> rootKey,
+        Consumer<InnerNode> patch
+    ) {
         // Preserve a copy of the super root to use it as a golden source of data.
         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(), EMPTY_CFG_SRC, true);
+        // Make a copy of the root inside the superRoot. This copy will be used for further patching.
+        superRoot.construct(rootKey.key(), EMPTY_CFG_SRC, true);
 
         // Patch root node.
-        patch.accept((ParentChange)superRoot.getRoot(ParentConfiguration.KEY));
+        patch.accept(superRoot.getRoot(rootKey));
 
         // Create flat diff between two super trees.
         return createFlattenedUpdatesMap(originalSuperRoot, superRoot);
@@ -690,8 +866,8 @@ public class ConfigurationUtilTest {
     /**
      * Simple root configuration schema.
      */
-    @ConfigurationRoot(rootName = "testRootSimple")
-    public static class SimpleRootConfigurationSchema {
+    @ConfigurationRoot(rootName = "rootInternal")
+    public static class InternalRootConfigurationSchema {
         /** String value without default. */
         @Value
         public String str0;
@@ -702,18 +878,18 @@ public class ConfigurationUtilTest {
 
         /** Sub configuration schema. */
         @ConfigValue
-        public SimpleConfigurationSchema subCfg;
+        public InternalConfigurationSchema subCfg;
 
         /** Named configuration schema. */
         @NamedConfigValue
-        public SimpleConfigurationSchema namedCfg;
+        public InternalConfigurationSchema namedCfg;
     }
 
     /**
      * Simple configuration schema.
      */
     @Config
-    public static class SimpleConfigurationSchema {
+    public static class InternalConfigurationSchema {
         /** String value with default. */
         @Value(hasDefault = true)
         public String str00 = "foo";
@@ -731,7 +907,7 @@ public class ConfigurationUtilTest {
      * First simple internal schema extension.
      */
     @InternalConfiguration
-    public static class InternalFirstSimpleConfigurationSchema extends SimpleConfigurationSchema {
+    public static class InternalFirstConfigurationSchema extends InternalConfigurationSchema {
         /** String value with default. */
         @Value(hasDefault = true)
         public String str01 = "foo";
@@ -741,7 +917,7 @@ public class ConfigurationUtilTest {
      * Second simple internal schema extension.
      */
     @InternalConfiguration
-    public static class InternalSecondSimpleConfigurationSchema extends SimpleConfigurationSchema {
+    public static class InternalSecondConfigurationSchema extends InternalConfigurationSchema {
         /** String value with default. */
         @Value(hasDefault = true)
         public String str02 = "foo";
@@ -751,7 +927,7 @@ public class ConfigurationUtilTest {
      * First root simple internal schema extension.
      */
     @InternalConfiguration
-    public static class InternalFirstSimpleRootConfigurationSchema extends SimpleRootConfigurationSchema {
+    public static class InternalFirstRootConfigurationSchema extends InternalRootConfigurationSchema {
         /** Second string value without default. */
         @Value
         public String str2;
@@ -765,17 +941,17 @@ public class ConfigurationUtilTest {
      * Second root simple internal schema extension.
      */
     @InternalConfiguration
-    public static class InternalSecondSimpleRootConfigurationSchema extends SimpleRootConfigurationSchema {
+    public static class InternalSecondRootConfigurationSchema extends InternalRootConfigurationSchema {
         /** Second sub configuration schema. */
         @ConfigValue
-        public SimpleConfigurationSchema subCfg1;
+        public InternalConfigurationSchema subCfg1;
     }
 
     /**
      * Internal extended simple root configuration schema.
      */
     @InternalConfiguration
-    public static class InternalExtendedSimpleRootConfigurationSchema extends SimpleRootConfigurationSchema {
+    public static class InternalExtendedRootConfigurationSchema extends InternalRootConfigurationSchema {
         /** String value without default. */
         @Value
         public String str00;
@@ -786,20 +962,68 @@ public class ConfigurationUtilTest {
 
         /** Sub configuration schema. */
         @ConfigValue
-        public SimpleConfigurationSchema subCfg0;
+        public InternalConfigurationSchema subCfg0;
 
         /** Named configuration schema. */
         @NamedConfigValue
-        public SimpleConfigurationSchema namedCfg0;
+        public InternalConfigurationSchema namedCfg0;
     }
 
     /**
      * Error: Duplicate field.
      */
     @InternalConfiguration
-    public static class ErrorInternalExtendedSimpleRootConfigurationSchema extends SimpleRootConfigurationSchema {
+    public static class ErrorInternalExtendedRootConfigurationSchema extends InternalRootConfigurationSchema {
         /** String value without default. */
         @Value
         public String str00;
     }
+
+    /**
+     * Simple root polymorphic configuration.
+     */
+    @ConfigurationRoot(rootName = "rootPolymorphic")
+    public static class PolymorphicRootConfigurationSchema {
+        /** Polymorphic sub configuration schema. */
+        @ConfigValue
+        public PolymorphicConfigurationSchema polymorphicSubCfg;
+
+        /** Polymorphic named configuration schema. */
+        @NamedConfigValue
+        public PolymorphicConfigurationSchema polymorphicNamedCfg;
+    }
+
+    /**
+     * Simple polymorphic configuration.
+     */
+    @PolymorphicConfig
+    public static class PolymorphicConfigurationSchema {
+        /** Polymorphic type id field. */
+        @PolymorphicId(hasDefault = true)
+        public String typeId = "first";
+
+        /** Long value. */
+        @Value(hasDefault = true)
+        public long longVal = 0;
+    }
+
+    /**
+     * First {@link PolymorphicConfigurationSchema} extension.
+     */
+    @PolymorphicConfigInstance("first")
+    public static class FirstPolymorphicInstanceConfigurationSchema extends PolymorphicConfigurationSchema {
+        /** String value. */
+        @Value(hasDefault = true)
+        public String strVal = "strVal";
+    }
+
+    /**
+     * Second {@link PolymorphicConfigurationSchema} extension.
+     */
+    @PolymorphicConfigInstance("second")
+    public static class SecondPolymorphicInstanceConfigurationSchema extends PolymorphicConfigurationSchema {
+        /** Integer value. */
+        @Value(hasDefault = true)
+        public int intVal = 0;
+    }
 }
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java
index e394fe3..d1716ae 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java
+++ b/modules/configuration/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, Map.of());
+        cgen.compileRootSchema(ValidatedRootConfigurationSchema.class, Map.of(), Map.of());
     }
 
     @AfterAll
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 407ca7a..37f388a 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
@@ -31,6 +31,8 @@ import org.jetbrains.annotations.Nullable;
 
 import static java.util.Collections.addAll;
 import static java.util.Collections.emptyIterator;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.unmodifiableCollection;
 import static java.util.Collections.unmodifiableSet;
 
 /**
@@ -86,22 +88,60 @@ public final class CollectionUtils {
      *
      * @param set Set.
      * @param ts Items.
-     * @param <T> Type of the elements of the list.
+     * @param <T> Type of the elements of set and items..
      * @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)
+
+        if (ts == null || ts.length == 0)
             return unmodifiableSet(set);
-        else {
-            Set<T> res = new HashSet<>(set);
 
-            addAll(res, ts);
+        Set<T> res = new HashSet<>(set);
 
-            return unmodifiableSet(res);
-        }
+        addAll(res, ts);
+
+        return unmodifiableSet(res);
+    }
+
+    /**
+     * Union collections.
+     *
+     * @param collections Collections.
+     * @param <T> Type of the elements of collections.
+     * @return Immutable union of collections.
+     */
+    @SafeVarargs
+    public static <T> Collection<T> union(Collection<T>... collections) {
+        if (collections == null || collections.length == 0)
+            return List.of();
+
+        return new AbstractCollection<>() {
+            /** Total size of the collections. */
+            int size = -1;
+
+            /** {@inheritDoc} */
+            @Override public Iterator<T> iterator() {
+                return concat(collections).iterator();
+            }
+
+            /** {@inheritDoc} */
+            @Override public int size() {
+                if (size == -1) {
+                    int s = 0;
+
+                    for (Collection<T> collection : collections) {
+                        s += collection.size();
+                    }
+
+                    size = s;
+                }
+
+                return size;
+            }
+        };
     }
 
     /**
@@ -117,31 +157,30 @@ public final class CollectionUtils {
     public static <T> Iterable<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();
+        return () -> new Iterator<>() {
+            /** Current index at {@code iterables}. */
+            int i = 0;
 
-                /** {@inheritDoc} */
-                @Override public boolean hasNext() {
-                    while (!curr.hasNext() && i < iterables.length)
-                        curr = iterables[i++].iterator();
+            /** Current iterator. */
+            Iterator<? extends T> curr = emptyIterator();
 
-                    return curr.hasNext();
-                }
+            /** {@inheritDoc} */
+            @Override public boolean hasNext() {
+                while (!curr.hasNext() && i < iterables.length)
+                    curr = iterables[i++].iterator();
 
-                /** {@inheritDoc} */
-                @Override public T next() {
-                    if (!hasNext())
-                        throw new NoSuchElementException();
-                    else
-                        return curr.next();
-                }
-            };
-        }
+                return curr.hasNext();
+            }
+
+            /** {@inheritDoc} */
+            @Override public T next() {
+                if (!hasNext())
+                    throw new NoSuchElementException();
+                else
+                    return curr.next();
+            }
+        };
     }
 
     /**
@@ -158,39 +197,69 @@ public final class CollectionUtils {
         @Nullable Function<? super T1, ? extends T2> mapper
     ) {
         if (nullOrEmpty(collection))
-            return Collections.emptyList();
-        else if (mapper == null)
-            return (Collection<T2>)collection;
-        else {
-            return new AbstractCollection<>() {
-                /** {@inheritDoc} */
-                @Override public Iterator<T2> iterator() {
-                    Iterator<? extends T1> iterator = collection.iterator();
-
-                    return new Iterator<>() {
-                        /** {@inheritDoc} */
-                        @Override public boolean hasNext() {
-                            return iterator.hasNext();
-                        }
-
-                        /** {@inheritDoc} */
-                        @Override public T2 next() {
-                            return mapper.apply(iterator.next());
-                        }
-                    };
-                }
+            return emptyList();
 
-                /** {@inheritDoc} */
-                @Override public int size() {
-                    return collection.size();
-                }
+        if (mapper == null)
+            return unmodifiableCollection((Collection<T2>)collection);
 
-                /** {@inheritDoc} */
-                @Override public boolean isEmpty() {
-                    return collection.isEmpty();
-                }
-            };
+        return new AbstractCollection<>() {
+            /** {@inheritDoc} */
+            @Override public Iterator<T2> iterator() {
+                Iterator<? extends T1> iterator = collection.iterator();
+
+                return new Iterator<>() {
+                    /** {@inheritDoc} */
+                    @Override public boolean hasNext() {
+                        return iterator.hasNext();
+                    }
+
+                    /** {@inheritDoc} */
+                    @Override public T2 next() {
+                        return mapper.apply(iterator.next());
+                    }
+                };
+            }
+
+            /** {@inheritDoc} */
+            @Override public int size() {
+                return collection.size();
+            }
+
+            /** {@inheritDoc} */
+            @Override public boolean isEmpty() {
+                return collection.isEmpty();
+            }
+        };
+    }
+
+    /**
+     * Difference of two sets.
+     *
+     * @param a First set.
+     * @param b Second set.
+     * @param <T> Type of the elements.
+     * @return Immutable set of elements of the first without the second.
+     */
+    public static <T> Set<T> difference(@Nullable Set<T> a, @Nullable Set<T> b) {
+        if (nullOrEmpty(a))
+            return Set.of();
+
+        else if (nullOrEmpty(b))
+            return unmodifiableSet(a);
+
+        // Lazy initialization.
+        Set<T> res = null;
+
+        for (T t : a) {
+            if (!b.contains(t)) {
+                if (res == null)
+                    res = new HashSet<>();
+
+                res.add(t);
+            }
         }
+
+        return res == null ? Set.of() : unmodifiableSet(res);
     }
 
     /** Stub. */
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
index 0724214..2484bf6 100644
--- 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
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.util;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.StreamSupport;
@@ -25,6 +26,7 @@ import org.junit.jupiter.api.Test;
 import static java.util.function.Function.identity;
 import static java.util.stream.Collectors.toList;
 import static org.apache.ignite.internal.util.CollectionUtils.concat;
+import static org.apache.ignite.internal.util.CollectionUtils.difference;
 import static org.apache.ignite.internal.util.CollectionUtils.union;
 import static org.apache.ignite.internal.util.CollectionUtils.viewReadOnly;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -51,17 +53,17 @@ public class CollectionUtilsTest {
 
     /** */
     @Test
-    void testUnion() {
+    void testSetUnion() {
         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), union(null, 1));
+        assertEquals(Set.of(1), union(Set.of(), 1));
 
-        assertEquals(Set.of(1, 2), union(Set.of(1), new Integer[] {2}));
+        assertEquals(Set.of(1, 2), union(Set.of(1), 2));
     }
 
     /** */
@@ -70,7 +72,7 @@ public class CollectionUtilsTest {
         assertTrue(viewReadOnly(null, null).isEmpty());
         assertTrue(viewReadOnly(List.of(), null).isEmpty());
 
-        assertEquals(List.of(1), viewReadOnly(List.of(1), null));
+        assertEquals(List.of(1), collect(viewReadOnly(List.of(1), null)));
         assertEquals(List.of(1), collect(viewReadOnly(List.of(1), identity())));
 
         assertEquals(List.of("1", "2", "3"), collect(viewReadOnly(List.of(1, 2, 3), String::valueOf)));
@@ -88,6 +90,36 @@ public class CollectionUtilsTest {
         assertThrows(UnsupportedOperationException.class, () -> viewReadOnly(List.of(1), null).iterator().remove());
     }
 
+    /** */
+    @Test
+    void testSetDifference() {
+        assertTrue(difference(null, Set.of(1, 2, 3, 4)).isEmpty());
+        assertTrue(difference(Set.of(), Set.of(1, 2, 3, 4)).isEmpty());
+
+        assertEquals(Set.of(1, 2, 3, 4), difference(Set.of(1, 2, 3, 4), null));
+        assertEquals(Set.of(1, 2, 3, 4), difference(Set.of(1, 2, 3, 4), Set.of()));
+
+        assertEquals(Set.of(1, 2), difference(Set.of(1, 2, 3, 4), Set.of(3, 4)));
+        assertEquals(Set.of(1, 4), difference(Set.of(1, 2, 3, 4), Set.of(2, 3)));
+
+        assertEquals(Set.of(1, 2, 3, 4), difference(Set.of(1, 2, 3, 4), Set.of(5, 6, 7)));
+
+        assertEquals(Set.of(), difference(Set.of(1, 2, 3, 4), Set.of(1, 2, 3, 4)));
+    }
+
+    /** */
+    @Test
+    void testCollectionUnion() {
+        assertTrue(union().isEmpty());
+        assertTrue(union(new Collection[] {}).isEmpty());
+        assertTrue(union(List.of()).isEmpty());
+
+        assertEquals(List.of(1), collect(union(List.of(1), List.of())));
+        assertEquals(List.of(1), collect(union(List.of(), List.of(1))));
+
+        assertEquals(List.of(1, 2), collect(union(List.of(1), List.of(2))));
+    }
+
     /**
      * Collect of elements.
      *
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 977f0ff..0e5bf10 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
@@ -69,6 +69,7 @@ public class ClusterServiceTestUtils {
             Collections.singleton(NetworkConfiguration.KEY),
             Map.of(),
             new TestConfigurationStorage(ConfigurationType.LOCAL),
+            List.of(),
             List.of()
         );
 
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 6c35c8f..2f0565c 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
@@ -71,6 +71,7 @@ public class ConfigurationPresentationTest {
             List.of(TestRootConfiguration.KEY),
             Map.of(Value.class, Set.of(validator)),
             new TestConfigurationStorage(LOCAL),
+            List.of(),
             List.of()
         );
 
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ITDistributedConfigurationPropertiesTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ITDistributedConfigurationPropertiesTest.java
index 8e9c2e8..ff4b683 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ITDistributedConfigurationPropertiesTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ITDistributedConfigurationPropertiesTest.java
@@ -136,6 +136,7 @@ public class ITDistributedConfigurationPropertiesTest {
                 List.of(NodeConfiguration.KEY),
                 Map.of(),
                 new LocalConfigurationStorage(vaultManager),
+                List.of(),
                 List.of()
             );
 
@@ -165,6 +166,7 @@ public class ITDistributedConfigurationPropertiesTest {
                 List.of(DistributedConfiguration.KEY),
                 Map.of(),
                 distributedCfgStorage,
+                List.of(),
                 List.of()
             );
         }
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ITDistributedConfigurationStorageTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ITDistributedConfigurationStorageTest.java
index f11dcda..6d85d1a 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ITDistributedConfigurationStorageTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ITDistributedConfigurationStorageTest.java
@@ -108,6 +108,7 @@ public class ITDistributedConfigurationStorageTest {
                 rootKeys,
                 Map.of(),
                 new LocalConfigurationStorage(vaultManager),
+                List.of(),
                 List.of()
             );
 
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 9d7ae20..ced4cce 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
@@ -156,6 +156,7 @@ public class IgniteImpl implements Ignite {
             ),
             Map.of(),
             new LocalConfigurationStorage(vaultMgr),
+            List.of(),
             List.of()
         );
 
@@ -186,7 +187,8 @@ public class IgniteImpl implements Ignite {
             ),
             Map.of(TableValidator.class, Set.of(TableValidatorImpl.INSTANCE)),
             new DistributedConfigurationStorage(metaStorageMgr, vaultMgr),
-            Collections.singletonList(ExtendedTableConfigurationSchema.class)
+            Collections.singletonList(ExtendedTableConfigurationSchema.class),
+            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 3046f31..31fb497 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
@@ -76,6 +76,7 @@ public class SchemaConfigurationConverterTest {
             List.of(TablesConfiguration.KEY, DataStorageConfiguration.KEY),
             Map.of(TableValidator.class, Set.of(TableValidatorImpl.INSTANCE)),
             new TestConfigurationStorage(DISTRIBUTED),
+            List.of(),
             List.of()
         );
 
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 88c0f90..39286ee 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
@@ -133,7 +133,7 @@ public class TableManagerTest extends IgniteAbstractTest {
     private Loza rm;
 
     /** Tables configuration. */
-    @InjectConfiguration(extensions = {ExtendedTableConfigurationSchema.class})
+    @InjectConfiguration(internalExtensions = {ExtendedTableConfigurationSchema.class})
     private TablesConfiguration tblsCfg;
 
     /** Data storage configuration. */