You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by se...@apache.org on 2020/12/17 12:51:30 UTC

[ignite-3] branch main updated: IGNITE-13562 Unified configuration: basic features and code generation tools

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

sergeychugunov 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 ef06617  IGNITE-13562 Unified configuration: basic features and code generation tools
ef06617 is described below

commit ef0661718edbad2f85bc864ca4bdd6f5b0ad1c2a
Author: Semyon Danilov <sa...@yandex.ru>
AuthorDate: Thu Dec 17 15:48:59 2020 +0300

    IGNITE-13562 Unified configuration: basic features and code generation tools
    
    Signed-off-by: Sergey Chugunov <se...@gmail.com>
---
 modules/configuration-annotation-processor/pom.xml | 113 +++
 .../internal/ConfigurationDescription.java         |  42 +
 .../processor/internal/ConfigurationElement.java   |  74 ++
 .../processor/internal/ConfigurationNode.java      |  53 ++
 .../processor/internal/Processor.java              | 910 +++++++++++++++++++++
 .../processor/internal/ProcessorException.java     |  32 +
 .../configuration/processor/internal/Utils.java    | 217 +++++
 .../internal/pojo/ChangeClassGenerator.java        |  87 ++
 .../processor/internal/pojo/ClassGenerator.java    | 127 +++
 .../processor/internal/pojo/FieldMapping.java      |  52 ++
 .../internal/pojo/InitClassGenerator.java          |  83 ++
 .../internal/pojo/ViewClassGenerator.java          |  79 ++
 .../internal/validation/ValidationGenerator.java   | 174 ++++
 .../services/javax.annotation.processing.Processor |   1 +
 .../processor/internal/AbstractProcessorTest.java  | 164 ++++
 .../processor/internal/ConfigSet.java              | 123 +++
 .../processor/internal/HasFieldMatcher.java        | 129 +++
 .../processor/internal/HasMethodMatcher.java       | 126 +++
 .../processor/internal/ParsedClass.java            | 107 +++
 .../processor/internal/ProcessorTest.java          | 104 +++
 .../configuration/processor/internal/Types.java    |  32 +
 .../sample/AutoAdjustConfigurationSchema.java      |  42 +
 .../sample/BaselineConfigurationSchema.java        |  37 +
 .../sample/CacheConfigurationSchema.java           |  33 +
 .../sample/ClusterWideConfigurationSchema.java     |  36 +
 .../sample/LocalConfigurationSchema.java           |  32 +
 .../sample/NodeConfigurationSchema.java            |  45 +
 .../ignite/configuration/sample/UsageTest.java     |  95 +++
 .../sample/validation/AutoAdjustValidator.java     |  41 +
 .../sample/validation/AutoAdjustValidator2.java    |  41 +
 .../sample/validation/NodeValidator.java           |  40 +
 .../internal/TestConfigurationSchema.java          |  22 +
 modules/configuration/pom.xml                      |  69 ++
 .../configuration/ConfigurationProperty.java       |  46 ++
 .../ignite/configuration/ConfigurationTree.java    |  30 +
 .../ignite/configuration/ConfigurationValue.java   |  25 +
 .../apache/ignite/configuration/Configurator.java  | 207 +++++
 .../ignite/configuration/PropertyListener.java     |  61 ++
 .../ignite/configuration/PublicConfigurator.java   |  42 +
 .../ignite/configuration/annotation/Config.java    |  69 ++
 .../configuration/annotation/ConfigValue.java      |  47 ++
 .../configuration/annotation/NamedConfigValue.java |  51 ++
 .../ignite/configuration/annotation/Validate.java  |  75 ++
 .../ignite/configuration/annotation/Value.java     |  40 +
 .../internal/DynamicConfiguration.java             | 150 ++++
 .../configuration/internal/DynamicProperty.java    | 203 +++++
 .../ignite/configuration/internal/Modifier.java    |  48 ++
 .../ignite/configuration/internal/NamedList.java   |  44 +
 .../internal/NamedListConfiguration.java           | 126 +++
 .../internal/selector/BaseSelectors.java           | 157 ++++
 .../configuration/internal/selector/Selector.java  |  40 +
 .../selector/SelectorNotFoundException.java        |  33 +
 .../internal/validation/MaxValidator.java          |  44 +
 .../internal/validation/MemberKey.java             |  51 ++
 .../internal/validation/MinValidator.java          |  44 +
 .../internal/validation/NotNullValidator.java      |  41 +
 .../storage/ConfigurationStorage.java              |  56 ++
 .../configuration/storage/StorageException.java    |  36 +
 .../ConfigurationValidationException.java          |  30 +
 .../configuration/validation/FieldValidator.java   |  47 ++
 pom.xml                                            |   5 +
 61 files changed, 5210 insertions(+)

diff --git a/modules/configuration-annotation-processor/pom.xml b/modules/configuration-annotation-processor/pom.xml
new file mode 100644
index 0000000..98758a8
--- /dev/null
+++ b/modules/configuration-annotation-processor/pom.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  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.
+-->
+
+<!--
+    POM file.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.ignite</groupId>
+    <artifactId>ignite-configuration-annotation-processor</artifactId>
+    <version>3.0-SNAPSHOT</version>
+    <url>http://ignite.apache.org</url>
+
+    <dependencies>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <version>1.2.17</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.squareup</groupId>
+            <artifactId>javapoet</artifactId>
+            <version>1.13.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-configuration</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>3.4.6</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.testing.compile</groupId>
+            <artifactId>compile-testing</artifactId>
+            <version>0.19</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <version>5.6.2</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>fr.inria.gforge.spoon</groupId>
+            <artifactId>spoon-core</artifactId>
+            <version>8.3.0</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <!-- disable processing because the definition in META-INF/services breaks javac -->
+<!--                    <compilerArgument>-proc:none</compilerArgument>-->
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>default-compile</id>
+                        <configuration>
+                            <compilerArgument>-proc:none</compilerArgument>
+                            <includes>
+                                <include>org/apache/ignite/configuration/processor/internal/*</include>
+                            </includes>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>compile-project</id>
+                        <phase>compile</phase>
+                        <goals>
+                            <goal>compile</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationDescription.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationDescription.java
new file mode 100644
index 0000000..2154aba
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationDescription.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.processor.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+import com.squareup.javapoet.TypeName;
+
+/**
+ * Configuration and all it's inner fields.
+ */
+public class ConfigurationDescription extends ConfigurationElement {
+    /** Inner configuration fields. */
+    private List<ConfigurationElement> fields = new ArrayList<>();
+
+    /** Constructor. */
+    public ConfigurationDescription(TypeName type, String name, TypeName view, TypeName init, TypeName change) {
+        super(type, name, view, init, change);
+    }
+
+    /**
+     * Get configuration fields.
+     */
+    public List<ConfigurationElement> getFields() {
+        return fields;
+    }
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationElement.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationElement.java
new file mode 100644
index 0000000..24bab53
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationElement.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.processor.internal;
+
+import com.squareup.javapoet.TypeName;
+
+/**
+ * Element of configuration.
+ */
+public class ConfigurationElement {
+    /** Name of configuration element. */
+    private final String name;
+
+    /** Configuration type. */
+    private final TypeName type;
+
+    /** Configuration VIEW type. */
+    private final TypeName view;
+
+    /** Configuration INIT type. */
+    private final TypeName init;
+
+    /** Configuration CHANGE type. */
+    private final TypeName change;
+
+    /** Constructor. */
+    public ConfigurationElement(TypeName type, String name, TypeName view, TypeName init, TypeName change) {
+        this.type = type;
+        this.name = name;
+        this.view = view;
+        this.init = init;
+        this.change = change;
+    }
+
+    /** */
+    public String getName() {
+        return name;
+    }
+
+    /** */
+    public TypeName getType() {
+        return type;
+    }
+
+    /** */
+    public TypeName getView() {
+        return view;
+    }
+
+    /** */
+    public TypeName getInit() {
+        return init;
+    }
+
+    /** */
+    public TypeName getChange() {
+        return change;
+    }
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationNode.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationNode.java
new file mode 100644
index 0000000..2bbd637
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ConfigurationNode.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.processor.internal;
+
+import com.squareup.javapoet.TypeName;
+
+/**
+ * Configuration element with a reference to its parent and with an original name.
+ */
+public class ConfigurationNode extends ConfigurationElement {
+    /** Configuration parent. */
+    private final ConfigurationNode parent;
+
+    /** Original name of configuration element. */
+    private final String originalName;
+
+    /** Constructor. */
+    public ConfigurationNode(TypeName type, String name, String originalName, TypeName view, TypeName init, TypeName change, ConfigurationNode parent) {
+        super(type, name, view, init, change);
+        this.originalName = originalName;
+        this.parent = parent;
+    }
+
+    /**
+     * Get configuration parent.
+     */
+    public ConfigurationNode getParent() {
+        return parent;
+    }
+
+    /**
+     * Get original name of configuration element.
+     */
+    public String getOriginalName() {
+        return originalName;
+    }
+
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
new file mode 100644
index 0000000..50d10c9
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Processor.java
@@ -0,0 +1,910 @@
+/*
+ * 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.processor.internal;
+
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.TypeSpec;
+import com.squareup.javapoet.WildcardTypeName;
+import java.io.IOException;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.util.Elements;
+import org.apache.ignite.configuration.ConfigurationTree;
+import org.apache.ignite.configuration.ConfigurationValue;
+import org.apache.ignite.configuration.Configurator;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.internal.DynamicConfiguration;
+import org.apache.ignite.configuration.internal.DynamicProperty;
+import org.apache.ignite.configuration.internal.NamedListConfiguration;
+import org.apache.ignite.configuration.internal.selector.BaseSelectors;
+import org.apache.ignite.configuration.internal.selector.Selector;
+import org.apache.ignite.configuration.internal.validation.MemberKey;
+import org.apache.ignite.configuration.processor.internal.pojo.ChangeClassGenerator;
+import org.apache.ignite.configuration.processor.internal.pojo.InitClassGenerator;
+import org.apache.ignite.configuration.processor.internal.pojo.ViewClassGenerator;
+import org.apache.ignite.configuration.processor.internal.validation.ValidationGenerator;
+
+import static javax.lang.model.element.Modifier.ABSTRACT;
+import static javax.lang.model.element.Modifier.FINAL;
+import static javax.lang.model.element.Modifier.PRIVATE;
+import static javax.lang.model.element.Modifier.PUBLIC;
+import static javax.lang.model.element.Modifier.STATIC;
+
+/**
+ * Annotation processor that produces configuration classes.
+ */
+public class Processor extends AbstractProcessor {
+    /** Wildcard (?) TypeName. */
+    private static final TypeName WILDCARD = WildcardTypeName.subtypeOf(Object.class);
+
+    /** Type of Configurator (every DynamicConfiguration has a Configurator field). */
+    private static final ParameterizedTypeName CONFIGURATOR_TYPE = ParameterizedTypeName.get(
+        ClassName.get(Configurator.class),
+        WildcardTypeName.subtypeOf(
+            ParameterizedTypeName.get(ClassName.get(DynamicConfiguration.class), WILDCARD, WILDCARD, WILDCARD)
+        )
+    );
+
+    /** Generator of VIEW classes. */
+    private ViewClassGenerator viewClassGenerator;
+
+    /** Generator of INIT classes. */
+    private InitClassGenerator initClassGenerator;
+
+    /** Generator of CHANGE classes. */
+    private ChangeClassGenerator changeClassGenerator;
+
+    /** Class file writer. */
+    private Filer filer;
+
+    /**
+     * Constructor.
+     */
+    public Processor() {
+    }
+
+    /** {@inheritDoc} */
+    @Override public synchronized void init(ProcessingEnvironment processingEnv) {
+        super.init(processingEnv);
+
+        filer = processingEnv.getFiler();
+        viewClassGenerator = new ViewClassGenerator(processingEnv);
+        initClassGenerator = new InitClassGenerator(processingEnv);
+        changeClassGenerator = new ChangeClassGenerator(processingEnv);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
+        final Elements elementUtils = processingEnv.getElementUtils();
+
+        Map<TypeName, ConfigurationDescription> props = new HashMap<>();
+
+        List<ConfigurationDescription> roots = new ArrayList<>();
+
+        // Package to use for Selectors and Keys classes
+        String packageForUtil = "";
+
+        // All classes annotated with @Config
+        final Set<TypeElement> annotatedConfigs = roundEnvironment.getElementsAnnotatedWith(Config.class).stream()
+            .filter(element -> element.getKind() == ElementKind.CLASS)
+            .map(TypeElement.class::cast)
+            .collect(Collectors.toSet());
+
+        if (annotatedConfigs.isEmpty())
+            return false;
+
+        for (TypeElement clazz : annotatedConfigs) {
+            // Get package name of the schema class
+            final PackageElement elementPackage = elementUtils.getPackageOf(clazz);
+            final String packageName = elementPackage.getQualifiedName().toString();
+
+            // Find all the fields of the schema
+            final List<VariableElement> fields = clazz.getEnclosedElements().stream()
+                .filter(el -> el.getKind() == ElementKind.FIELD)
+                .map(VariableElement.class::cast)
+                .collect(Collectors.toList());
+
+            final Config classConfigAnnotation = clazz.getAnnotation(Config.class);
+
+            // Configuration name
+            final String configName = classConfigAnnotation.value();
+            // Is root of the configuration
+            final boolean isRoot = classConfigAnnotation.root();
+            final ClassName schemaClassName = ClassName.get(packageName, clazz.getSimpleName().toString());
+
+            // Get name for generated configuration class and it's interface
+            final ClassName configClass = Utils.getConfigurationName(schemaClassName);
+            final ClassName configInterface = Utils.getConfigurationInterfaceName(schemaClassName);
+
+            ConfigurationDescription configDesc = new ConfigurationDescription(
+                configClass,
+                configName,
+                Utils.getViewName(schemaClassName),
+                Utils.getInitName(schemaClassName),
+                Utils.getChangeName(schemaClassName)
+            );
+
+            // If root, then use it's package as package for Selectors and Keys
+            if (isRoot) {
+                roots.add(configDesc);
+                packageForUtil = packageName;
+            }
+
+            TypeSpec.Builder configurationClassBuilder = TypeSpec.classBuilder(configClass)
+                .addSuperinterface(configInterface)
+                .addModifiers(PUBLIC, FINAL);
+
+            TypeSpec.Builder configurationInterfaceBuilder = TypeSpec.interfaceBuilder(configInterface)
+                .addModifiers(PUBLIC);
+
+            CodeBlock.Builder constructorBodyBuilder = CodeBlock.builder();
+            CodeBlock.Builder copyConstructorBodyBuilder = CodeBlock.builder();
+
+            for (VariableElement field : fields) {
+                // Get original field type (must be another configuration schema or "primitive" like String or long)
+                final TypeName baseType = TypeName.get(field.asType());
+
+                final String fieldName = field.getSimpleName().toString();
+
+                // Get configuration types (VIEW, INIT, CHANGE and so on)
+                final ConfigurationFieldTypes types = getTypes(field);
+
+                TypeName getMethodType = types.getGetMethodType();
+                TypeName viewClassType = types.getViewClassType();
+                TypeName initClassType = types.getInitClassType();
+                TypeName changeClassType = types.getChangeClassType();
+
+                final ConfigValue confAnnotation = field.getAnnotation(ConfigValue.class);
+                if (confAnnotation != null) {
+                    // Create DynamicConfiguration (descendant) field
+                    final FieldSpec nestedConfigField =
+                        FieldSpec
+                            .builder(getMethodType, fieldName, Modifier.PRIVATE, FINAL)
+                            .build();
+
+                    configurationClassBuilder.addField(nestedConfigField);
+
+                    // Constructor statement
+                    constructorBodyBuilder.addStatement("add($L = new $T(qualifiedName, $S, false, configurator, this.root))", fieldName, getMethodType, fieldName);
+
+                    // Copy constructor statement
+                    copyConstructorBodyBuilder.addStatement("add($L = base.$L.copy(this.root))", fieldName, fieldName);
+                }
+
+                final NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
+                if (namedConfigAnnotation != null) {
+                    ClassName fieldType = Utils.getConfigurationName((ClassName) baseType);
+
+                    // Create NamedListConfiguration<> field
+                    final FieldSpec nestedConfigField = FieldSpec.builder(
+                        getMethodType,
+                        fieldName,
+                        Modifier.PRIVATE,
+                        FINAL
+                    ).build();
+
+                    configurationClassBuilder.addField(nestedConfigField);
+
+                    // Constructor statement
+                    constructorBodyBuilder.addStatement(
+                        "add($L = new $T(qualifiedName, $S, configurator, this.root, (p, k) -> new $T(p, k, true, configurator, this.root)))",
+                        fieldName,
+                        getMethodType,
+                        fieldName,
+                        fieldType
+                    );
+
+                    // Copy constructor statement
+                    copyConstructorBodyBuilder.addStatement("add($L = base.$L.copy(this.root))", fieldName, fieldName);
+                }
+
+                final Value valueAnnotation = field.getAnnotation(Value.class);
+                if (valueAnnotation != null) {
+                    // Create value (DynamicProperty<>) field
+                    final FieldSpec generatedField = FieldSpec.builder(getMethodType, fieldName, Modifier.PRIVATE, FINAL).build();
+
+                    configurationClassBuilder.addField(generatedField);
+
+                    final CodeBlock validatorsBlock = ValidationGenerator.generateValidators(field);
+
+                    // Constructor statement
+                    constructorBodyBuilder.addStatement(
+                        "add($L = new $T(qualifiedName, $S, new $T($T.class, $S), this.configurator, this.root), $L)",
+                        fieldName, getMethodType, fieldName, MemberKey.class, configClass, fieldName, validatorsBlock
+                    );
+
+                    // Copy constructor statement
+                    copyConstructorBodyBuilder.addStatement("add($L = base.$L.copy(this.root))", fieldName, fieldName);
+                }
+
+                configDesc.getFields().add(new ConfigurationElement(getMethodType, fieldName, viewClassType, initClassType, changeClassType));
+
+                createGettersAndSetter(configurationClassBuilder, configurationInterfaceBuilder, fieldName, types, valueAnnotation);
+            }
+
+            props.put(configClass, configDesc);
+
+            // Create VIEW, INIT and CHANGE classes
+            createPojoBindings(packageName, fields, schemaClassName, configurationClassBuilder, configurationInterfaceBuilder);
+
+            // Create constructors for configuration class
+            createConstructors(configClass, configName, configurationClassBuilder, CONFIGURATOR_TYPE, constructorBodyBuilder, copyConstructorBodyBuilder);
+
+            // Create copy method for configuration class
+            createCopyMethod(configClass, configurationClassBuilder);
+
+            // Write configuration interface
+            JavaFile interfaceFile = JavaFile.builder(packageName, configurationInterfaceBuilder.build()).build();
+
+            try {
+                interfaceFile.writeTo(filer);
+            } catch (IOException e) {
+                throw new ProcessorException("Failed to create configuration class " + configClass.toString(), e);
+            }
+
+            // Write configuration
+            JavaFile classFile = JavaFile.builder(packageName, configurationClassBuilder.build()).build();
+
+            try {
+                classFile.writeTo(filer);
+            } catch (IOException e) {
+                throw new ProcessorException("Failed to create configuration class " + configClass.toString(), e);
+            }
+        }
+
+        // Get all generated configuration nodes
+        final List<ConfigurationNode> flattenConfig = roots.stream()
+            .map((ConfigurationDescription cfg) -> buildConfigForest(cfg, props))
+            .flatMap(Set::stream)
+            .collect(Collectors.toList());
+
+        // Generate Keys class
+        createKeysClass(packageForUtil, flattenConfig);
+
+        // Generate Selectors class
+        createSelectorsClass(packageForUtil, flattenConfig);
+
+        return true;
+    }
+
+    /**
+     * Create getters and setters for configuration class.
+     *
+     * @param configurationClassBuilder
+     * @param configurationInterfaceBuilder
+     * @param fieldName
+     * @param types
+     * @param valueAnnotation
+     */
+    private void createGettersAndSetter(
+        TypeSpec.Builder configurationClassBuilder,
+        TypeSpec.Builder configurationInterfaceBuilder,
+        String fieldName,
+        ConfigurationFieldTypes types,
+        Value valueAnnotation
+    ) {
+        MethodSpec interfaceGetMethod = MethodSpec.methodBuilder(fieldName)
+            .addModifiers(PUBLIC, ABSTRACT)
+            .returns(types.getInterfaceGetMethodType())
+            .build();
+        configurationInterfaceBuilder.addMethod(interfaceGetMethod);
+
+        MethodSpec getMethod = MethodSpec.methodBuilder(fieldName)
+            .addModifiers(PUBLIC, FINAL)
+            .returns(types.getGetMethodType())
+            .addStatement("return $L", fieldName)
+            .build();
+        configurationClassBuilder.addMethod(getMethod);
+
+        if (valueAnnotation != null) {
+            MethodSpec setMethod = MethodSpec
+                .methodBuilder(fieldName)
+                .addModifiers(PUBLIC, FINAL)
+                .addParameter(types.getUnwrappedType(), fieldName)
+                .addStatement("this.$L.change($L)", fieldName, fieldName)
+                .build();
+            configurationClassBuilder.addMethod(setMethod);
+        }
+    }
+
+    /**
+     * Get types for configuration classes generation.
+     * @param field
+     * @return Bundle with all types for configuration
+     */
+    private ConfigurationFieldTypes getTypes(final VariableElement field) {
+        TypeName getMethodType = null;
+        TypeName interfaceGetMethodType = null;
+
+        final TypeName baseType = TypeName.get(field.asType());
+
+        TypeName unwrappedType = baseType;
+        TypeName viewClassType = baseType;
+        TypeName initClassType = baseType;
+        TypeName changeClassType = baseType;
+
+        final ConfigValue confAnnotation = field.getAnnotation(ConfigValue.class);
+        if (confAnnotation != null) {
+            getMethodType = Utils.getConfigurationName((ClassName) baseType);
+            interfaceGetMethodType = Utils.getConfigurationInterfaceName((ClassName) baseType);
+
+            unwrappedType = getMethodType;
+            viewClassType = Utils.getViewName((ClassName) baseType);
+            initClassType = Utils.getInitName((ClassName) baseType);
+            changeClassType = Utils.getChangeName((ClassName) baseType);
+        }
+
+        final NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
+        if (namedConfigAnnotation != null) {
+            ClassName fieldType = Utils.getConfigurationName((ClassName) baseType);
+
+            viewClassType = Utils.getViewName((ClassName) baseType);
+            initClassType = Utils.getInitName((ClassName) baseType);
+            changeClassType = Utils.getChangeName((ClassName) baseType);
+
+            getMethodType = ParameterizedTypeName.get(ClassName.get(NamedListConfiguration.class), viewClassType, fieldType, initClassType, changeClassType);
+            interfaceGetMethodType = ParameterizedTypeName.get(ClassName.get(NamedListConfiguration.class), viewClassType, fieldType, initClassType, changeClassType);
+        }
+
+        final Value valueAnnotation = field.getAnnotation(Value.class);
+        if (valueAnnotation != null) {
+            ClassName dynPropClass = ClassName.get(DynamicProperty.class);
+            ClassName confValueClass = ClassName.get(ConfigurationValue.class);
+
+            TypeName genericType = baseType;
+
+            if (genericType.isPrimitive()) {
+                genericType = genericType.box();
+            }
+
+            getMethodType = ParameterizedTypeName.get(dynPropClass, genericType);
+            interfaceGetMethodType = ParameterizedTypeName.get(confValueClass, genericType);
+        }
+
+        return new ConfigurationFieldTypes(getMethodType, unwrappedType, viewClassType, initClassType, changeClassType, interfaceGetMethodType);
+    }
+
+    /**
+     * Wrapper for configuration schema types.
+     */
+    private static class ConfigurationFieldTypes {
+        /** Field get method type. */
+        private final TypeName getMethodType;
+
+        /** Configuration type (if marked with @ConfigValue or @NamedConfig), or original type (if marked with @Value) */
+        private final TypeName unwrappedType;
+
+        /** VIEW object type. */
+        private final TypeName viewClassType;
+
+        /** INIT object type. */
+        private final TypeName initClassType;
+
+        /** CHANGE object type. */
+        private final TypeName changeClassType;
+
+        /** Get method type for public interface. */
+        private final TypeName interfaceGetMethodType;
+
+        public ConfigurationFieldTypes(TypeName getMethodType, TypeName unwrappedType, TypeName viewClassType, TypeName initClassType, TypeName changeClassType, TypeName interfaceGetMethodType) {
+            this.getMethodType = getMethodType;
+            this.unwrappedType = unwrappedType;
+            this.viewClassType = viewClassType;
+            this.initClassType = initClassType;
+            this.changeClassType = changeClassType;
+            this.interfaceGetMethodType = interfaceGetMethodType;
+        }
+
+        /** */
+        public TypeName getInterfaceGetMethodType() {
+            return interfaceGetMethodType;
+        }
+
+        /** */
+        public TypeName getGetMethodType() {
+            return getMethodType;
+        }
+
+        /** */
+        public TypeName getUnwrappedType() {
+            return unwrappedType;
+        }
+
+        /** */
+        public TypeName getViewClassType() {
+            return viewClassType;
+        }
+
+        /** */
+        public TypeName getInitClassType() {
+            return initClassType;
+        }
+
+        /** */
+        public TypeName getChangeClassType() {
+            return changeClassType;
+        }
+    }
+
+    /**
+     * Create copy-method for configuration class.
+     *
+     * @param configClass Configuration class name.
+     * @param configurationClassBuilder Configuration class builder.
+     */
+    private void createCopyMethod(ClassName configClass, TypeSpec.Builder configurationClassBuilder) {
+        MethodSpec copyMethod = MethodSpec.methodBuilder("copy")
+            .addAnnotation(Override.class)
+            .addModifiers(PUBLIC)
+            .addParameter(DynamicConfiguration.class, "root")
+            .returns(configClass)
+            .addStatement("return new $T(this, root)", configClass)
+            .build();
+
+        configurationClassBuilder.addMethod(copyMethod);
+    }
+
+    /**
+     * Create configuration class constructors.
+     *
+     * @param configClass Configuration class name.
+     * @param configName Configuration name.
+     * @param configurationClassBuilder Configuration class builder.
+     * @param configuratorClassName Configurator (configuration wrapper) class name.
+     * @param constructorBodyBuilder Constructor body.
+     * @param copyConstructorBodyBuilder Copy constructor body.
+     */
+    private void createConstructors(
+        ClassName configClass,
+        String configName,
+        TypeSpec.Builder configurationClassBuilder,
+        ParameterizedTypeName configuratorClassName,
+        CodeBlock.Builder constructorBodyBuilder,
+        CodeBlock.Builder copyConstructorBodyBuilder
+    ) {
+        final MethodSpec constructorWithName = MethodSpec.constructorBuilder()
+            .addModifiers(PUBLIC)
+            .addParameter(String.class, "prefix")
+            .addParameter(String.class, "key")
+            .addParameter(boolean.class, "isNamed")
+            .addParameter(configuratorClassName, "configurator")
+            .addParameter(DynamicConfiguration.class, "root")
+            .addStatement("super(prefix, key, isNamed, configurator, root)")
+            .addCode(constructorBodyBuilder.build())
+            .build();
+        configurationClassBuilder.addMethod(constructorWithName);
+
+        final MethodSpec copyConstructor = MethodSpec.constructorBuilder()
+            .addModifiers(PRIVATE)
+            .addParameter(configClass, "base")
+            .addParameter(DynamicConfiguration.class, "root")
+            .addStatement("super(base.prefix, base.key, base.isNamed, base.configurator, root)")
+            .addCode(copyConstructorBodyBuilder.build())
+            .build();
+        configurationClassBuilder.addMethod(copyConstructor);
+
+        final MethodSpec emptyConstructor = MethodSpec.constructorBuilder()
+                .addModifiers(PUBLIC)
+                .addParameter(configuratorClassName, "configurator")
+                .addStatement("this($S, $S, false, configurator, null)", "", configName)
+                .build();
+
+        configurationClassBuilder.addMethod(emptyConstructor);
+    }
+
+    /**
+     * Create selectors.
+     *
+     * @param packageForUtil Package to place selectors class to.
+     * @param flattenConfig List of configuration nodes.
+     */
+    private void createSelectorsClass(String packageForUtil, List<ConfigurationNode> flattenConfig) {
+        ClassName selectorsClassName = ClassName.get(packageForUtil, "Selectors");
+
+        final TypeSpec.Builder selectorsClass = TypeSpec.classBuilder(selectorsClassName)
+            .superclass(BaseSelectors.class)
+            .addModifiers(PUBLIC, FINAL);
+
+        final CodeBlock.Builder selectorsStaticBlockBuilder = CodeBlock.builder();
+        selectorsStaticBlockBuilder.addStatement("$T publicLookup = $T.publicLookup()", MethodHandles.Lookup.class, MethodHandles.class);
+
+        selectorsStaticBlockBuilder.beginControlFlow("try");
+
+        // For every configuration node create selector (based on a method call chain)
+        for (ConfigurationNode configNode : flattenConfig) {
+            String regex = "([a-z])([A-Z]+)";
+            String replacement = "$1_$2";
+
+            // Selector variable name (like LOCAL_BASELINE_AUTO_ADJUST_ENABLED)
+            final String varName = configNode.getName()
+                .replaceAll(regex, replacement)
+                .toUpperCase()
+                .replace(".", "_");
+
+            TypeName type = configNode.getType();
+
+            if (Utils.isNamedConfiguration(type))
+                type = Utils.unwrapNamedListConfigurationClass(type);
+
+            StringBuilder methodCall = new StringBuilder();
+
+            ConfigurationNode current = configNode;
+            ConfigurationNode root = null;
+            int namedCount = 0;
+
+            // Walk from node up to the root and create a method call chain
+            while (current != null) {
+                boolean isNamed = false;
+
+                if (Utils.isNamedConfiguration(current.getType())) {
+                    namedCount++;
+                    isNamed = true;
+                }
+
+                if (current.getParent() != null) {
+                    String newMethodCall = "." + current.getOriginalName() + "()";
+
+                    // if config is named, then create a call with name parameter
+                    if (isNamed)
+                        newMethodCall += ".get(name" + (namedCount - 1) + ")";
+
+                    methodCall.insert(0, newMethodCall);
+                } else
+                    root = current;
+
+                current = current.getParent();
+            }
+
+            TypeName selectorRec = Utils.getParameterized(ClassName.get(Selector.class), root.getType(), type, configNode.getView(), configNode.getInit(), configNode.getChange());
+
+            if (namedCount > 0) {
+                final MethodSpec.Builder builder = MethodSpec.methodBuilder(varName);
+
+                for (int i = 0; i < namedCount; i++) {
+                    builder.addParameter(String.class, "name" + i);
+                }
+
+                selectorsClass.addMethod(
+                    builder
+                        .returns(selectorRec)
+                        .addModifiers(PUBLIC, STATIC, FINAL)
+                        .addStatement("return (root) -> root$L", methodCall.toString())
+                        .build()
+                );
+
+
+                // Build a list of parameters for statement
+                List<Object> params = new ArrayList<>();
+                params.add(MethodHandle.class);
+                params.add(varName);
+                params.add(selectorsClassName);
+                params.add(varName);
+                params.add(MethodType.class);
+                params.add(Selector.class);
+
+                // For every named config in call chain -- add String (name) parameter
+                for (int i = 0; i < namedCount; i++) {
+                    params.add(String.class);
+                }
+
+                // Create a string for name parameters
+                final String nameStringParameters = IntStream.range(0, namedCount).mapToObj(i -> "$T.class").collect(Collectors.joining(","));
+
+                selectorsStaticBlockBuilder.addStatement("$T $L = publicLookup.findStatic($T.class, $S, $T.methodType($T.class, " + nameStringParameters + "))", params.toArray());
+
+                selectorsStaticBlockBuilder.addStatement("put($S, $L)", configNode.getName(), varName);
+            }
+            else {
+                selectorsClass.addField(
+                    FieldSpec.builder(selectorRec, varName)
+                        .addModifiers(PUBLIC, STATIC, FINAL)
+                        .initializer("(root) -> root$L", methodCall.toString())
+                        .build()
+                );
+                selectorsStaticBlockBuilder.addStatement("put($S, $L)", configNode.getName(), varName);
+            }
+        }
+
+        selectorsStaticBlockBuilder
+            .nextControlFlow("catch ($T e)", Exception.class)
+            .endControlFlow();
+
+        selectorsClass.addStaticBlock(selectorsStaticBlockBuilder.build());
+
+        JavaFile selectorsClassFile = JavaFile.builder(selectorsClassName.packageName(), selectorsClass.build()).build();
+        try {
+            selectorsClassFile.writeTo(filer);
+        }
+        catch (IOException e) {
+            throw new ProcessorException("Failed to write class: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Create keys class.
+     *
+     * @param packageForUtil Package to place keys class to.
+     * @param flattenConfig List of configuration nodes.
+     */
+    private void createKeysClass(String packageForUtil, List<ConfigurationNode> flattenConfig) {
+        final TypeSpec.Builder keysClass = TypeSpec.classBuilder("Keys").addModifiers(PUBLIC, FINAL);
+
+        for (ConfigurationNode node : flattenConfig) {
+            final String varName = node.getName().toUpperCase().replace(".", "_");
+            keysClass.addField(
+                FieldSpec.builder(String.class, varName)
+                    .addModifiers(PUBLIC, STATIC, FINAL)
+                    .initializer("$S", node.getName())
+                    .build()
+            );
+        }
+
+        JavaFile keysClassFile = JavaFile.builder(packageForUtil, keysClass.build()).build();
+        try {
+            keysClassFile.writeTo(filer);
+        } catch (IOException e) {
+            throw new ProcessorException("Failed to write class: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Create VIEW, INIT and CHANGE classes and methods.
+     *
+     * @param packageName Configuration package name.
+     * @param fields List of configuration fields.
+     * @param schemaClassName Class name of schema.
+     * @param configurationClassBuilder Configuration class builder.
+     */
+    private void createPojoBindings(
+        String packageName,
+        List<VariableElement> fields,
+        ClassName schemaClassName,
+        TypeSpec.Builder configurationClassBuilder,
+        TypeSpec.Builder configurationInterfaceBuilder
+    ) {
+        final ClassName viewClassTypeName = Utils.getViewName(schemaClassName);
+        final ClassName initClassName = Utils.getInitName(schemaClassName);
+        final ClassName changeClassName = Utils.getChangeName(schemaClassName);
+
+        ClassName dynConfClass = ClassName.get(DynamicConfiguration.class);
+        TypeName dynConfViewClassType = ParameterizedTypeName.get(dynConfClass, viewClassTypeName, initClassName, changeClassName);
+
+        configurationClassBuilder.superclass(dynConfViewClassType);
+
+        ClassName confTreeInterface = ClassName.get(ConfigurationTree.class);
+        TypeName confTreeParameterized = ParameterizedTypeName.get(confTreeInterface, viewClassTypeName, changeClassName);
+
+        configurationInterfaceBuilder.addSuperinterface(confTreeParameterized);
+
+        try {
+            viewClassGenerator.create(packageName, viewClassTypeName, fields);
+            final MethodSpec toViewMethod = createToViewMethod(viewClassTypeName, fields);
+            configurationClassBuilder.addMethod(toViewMethod);
+        }
+        catch (IOException e) {
+            throw new ProcessorException("Failed to write class " + viewClassTypeName.toString(), e);
+        }
+
+        try {
+            changeClassGenerator.create(packageName, changeClassName, fields);
+            final MethodSpec changeMethod = createChangeMethod(changeClassName, fields);
+            configurationClassBuilder.addMethod(changeMethod);
+        }
+        catch (IOException e) {
+            throw new ProcessorException("Failed to write class " + changeClassName.toString(), e);
+        }
+
+        try {
+            initClassGenerator.create(packageName, initClassName, fields);
+            final MethodSpec initMethod = createInitMethod(initClassName, fields);
+            configurationClassBuilder.addMethod(initMethod);
+        }
+        catch (IOException e) {
+            throw new ProcessorException("Failed to write class " + initClassName.toString(), e);
+        }
+    }
+
+    /**
+     * Build configuration forest base on root configuration description and all processed configurations.
+     *
+     * @param root Root configuration description.
+     * @param props All configurations.
+     * @return All possible config trees.
+     */
+    private Set<ConfigurationNode> buildConfigForest(ConfigurationDescription root, Map<TypeName, ConfigurationDescription> props) {
+        Set<ConfigurationNode> res = new HashSet<>();
+        Deque<ConfigurationNode> propsStack = new LinkedList<>();
+
+        ConfigurationNode rootNode = new ConfigurationNode(root.getType(), root.getName(), root.getName(), root.getView(), root.getInit(), root.getChange(), null);
+
+        propsStack.addFirst(rootNode);
+
+        // Walk through the all fields of every node and build a tree of configuration (more like chain)
+        while (!propsStack.isEmpty()) {
+            final ConfigurationNode current = propsStack.pollFirst();
+
+            // Get configuration type
+            TypeName type = current.getType();
+
+            if (Utils.isNamedConfiguration(type))
+                type = Utils.unwrapNamedListConfigurationClass(current.getType());
+
+            final ConfigurationDescription configDesc = props.get(type);
+
+            // Get fields of configuration
+            final List<ConfigurationElement> propertiesList = configDesc.getFields();
+
+            if (current.getName() != null && !current.getName().isEmpty())
+                // Add current node to result
+                res.add(current);
+
+            for (ConfigurationElement property : propertiesList) {
+                String qualifiedName = property.getName();
+
+                if (current.getName() != null && !current.getName().isEmpty())
+                    qualifiedName = current.getName() + "." + qualifiedName;
+
+                final ConfigurationNode newChainElement = new ConfigurationNode(
+                    property.getType(),
+                    qualifiedName,
+                    property.getName(),
+                    property.getView(),
+                    property.getInit(),
+                    property.getChange(),
+                    current
+                );
+
+                boolean isNamedConfig = false;
+                if (property.getType() instanceof ParameterizedTypeName) {
+                    final ParameterizedTypeName parameterized = (ParameterizedTypeName) property.getType();
+
+                    if (parameterized.rawType.equals(ClassName.get(NamedListConfiguration.class)))
+                        isNamedConfig = true;
+                }
+
+                if (props.containsKey(property.getType()) || isNamedConfig)
+                    // If it's not a leaf, add to stack
+                    propsStack.add(newChainElement);
+                else
+                    // otherwise, add to result
+                    res.add(newChainElement);
+
+            }
+        }
+        return res;
+    }
+
+    /**
+     * Create {@link org.apache.ignite.configuration.ConfigurationProperty#value} method for configuration class.
+     *
+     * @param type VIEW method type.
+     * @param variables List of VIEW object's fields.
+     * @return toView() method.
+     */
+    public MethodSpec createToViewMethod(TypeName type, List<VariableElement> variables) {
+        String args = variables.stream()
+            .map(v -> v.getSimpleName().toString() + ".value()")
+            .collect(Collectors.joining(", "));
+
+        final CodeBlock returnBlock = CodeBlock.builder()
+            .add("return new $T($L)", type, args)
+            .build();
+
+        return MethodSpec.methodBuilder("value")
+            .addModifiers(PUBLIC)
+            .addAnnotation(Override.class)
+            .returns(type)
+            .addStatement(returnBlock)
+            .build();
+    }
+
+    /**
+     * Create {@link org.apache.ignite.configuration.internal.Modifier#init(Object)} method (accepts INIT object) for configuration class.
+     *
+     * @param type INIT method type.
+     * @param variables List of INIT object's fields.
+     * @return Init method.
+     */
+    public MethodSpec createInitMethod(TypeName type, List<VariableElement> variables) {
+        final CodeBlock.Builder builder = CodeBlock.builder();
+
+        for (VariableElement variable : variables) {
+            final String name = variable.getSimpleName().toString();
+            builder.beginControlFlow("if (initial.$L() != null)", name);
+            builder.addStatement("$L.init(initial.$L())", name, name);
+            builder.endControlFlow();
+        }
+
+        return MethodSpec.methodBuilder("init")
+            .addModifiers(PUBLIC)
+            .addAnnotation(Override.class)
+            .addParameter(type, "initial")
+            .addCode(builder.build())
+            .build();
+    }
+
+    /**
+     * Create {@link org.apache.ignite.configuration.internal.Modifier#change(Object)} method (accepts CHANGE object) for configuration class.
+     *
+     * @param type CHANGE method type.
+     * @param variables List of CHANGE object's fields.
+     * @return Change method.
+     */
+    public MethodSpec createChangeMethod(TypeName type, List<VariableElement> variables) {
+        final CodeBlock.Builder builder = CodeBlock.builder();
+
+        for (VariableElement variable : variables) {
+            final Value valueAnnotation = variable.getAnnotation(Value.class);
+            if (valueAnnotation != null && valueAnnotation.immutable())
+                continue;
+
+            final String name = variable.getSimpleName().toString();
+            builder.beginControlFlow("if (changes.$L() != null)", name);
+            builder.addStatement("$L.changeWithoutValidation(changes.$L())", name, name);
+            builder.endControlFlow();
+        }
+
+        return MethodSpec.methodBuilder("changeWithoutValidation")
+            .addModifiers(PUBLIC)
+            .addAnnotation(Override.class)
+            .addParameter(type, "changes")
+            .addCode(builder.build())
+            .build();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Set<String> getSupportedAnnotationTypes() {
+        return Collections.singleton(Config.class.getCanonicalName());
+    }
+
+    /** {@inheritDoc} */
+    @Override public SourceVersion getSupportedSourceVersion() {
+        return SourceVersion.RELEASE_8;
+    }
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ProcessorException.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ProcessorException.java
new file mode 100644
index 0000000..90cd881
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/ProcessorException.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.processor.internal;
+
+/**
+ * Annotation processing exception.
+ */
+public class ProcessorException extends RuntimeException {
+    /** Constructor. */
+    public ProcessorException(String message) {
+        super(message);
+    }
+
+    /** Constructor. */
+    public ProcessorException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Utils.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Utils.java
new file mode 100644
index 0000000..a1e3687
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/Utils.java
@@ -0,0 +1,217 @@
+/*
+ * 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.processor.internal;
+
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.VariableElement;
+import org.apache.ignite.configuration.internal.DynamicConfiguration;
+import org.apache.ignite.configuration.internal.NamedListConfiguration;
+
+/**
+ * Annotation processing utilities.
+ */
+public class Utils {
+    /** Private constructor. */
+    private Utils() {
+    }
+
+    /**
+     * Create constructor for
+     *
+     * @param fieldSpecs List of fields.
+     * @return Constructor method.
+     */
+    public static MethodSpec createConstructor(List<FieldSpec> fieldSpecs) {
+        final MethodSpec.Builder builder = MethodSpec.constructorBuilder();
+
+        for (FieldSpec field : fieldSpecs) {
+            builder.addParameter(field.type, field.name);
+            builder.addStatement("this.$L = $L", field.name, field.name);
+        }
+
+        return builder.build();
+    }
+
+    /**
+     * Create getters for fields.
+     *
+     * @param fieldSpecs List of fields.
+     * @return List of getter methods.
+     */
+    public static List<MethodSpec> createGetters(List<FieldSpec> fieldSpecs) {
+        return fieldSpecs.stream().map(field ->
+            MethodSpec.methodBuilder(field.name)
+                .returns(field.type)
+                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+                .addStatement("return $L", field.name)
+                .build()).collect(Collectors.toList()
+        );
+    }
+
+    /**
+     * Create builder-style setters.
+     *
+     * @param fieldSpecs List of fields.
+     * @return List of setter methods.
+     */
+    public static List<MethodSpec> createBuildSetters(List<FieldSpec> fieldSpecs) {
+        return fieldSpecs.stream().map(field -> {
+            return MethodSpec.methodBuilder("with" + field.name)
+                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+                .addStatement("this.$L = $L", field.name, field.name)
+                .build();
+        }).collect(Collectors.toList());
+    }
+
+    /**
+     * Create '{@code new SomeObject(arg1, arg2, ..., argN)}' code block.
+     *
+     * @param type Type of the new object.
+     * @param fieldSpecs List of arguments.
+     * @return New object code block.
+     */
+    public static CodeBlock newObject(TypeName type, List<VariableElement> fieldSpecs) {
+        String args = fieldSpecs.stream().map(f -> f.getSimpleName().toString()).collect(Collectors.joining(", "));
+        return CodeBlock.builder()
+            .add("new $T($L)", type, args)
+            .build();
+    }
+
+    /**
+     * Get class with parameters, boxing them if necessary.
+     *
+     * @param clz Generic class.
+     * @param types Generic parameters.
+     * @return Parameterized type.
+     */
+    public static ParameterizedTypeName getParameterized(ClassName clz, TypeName... types) {
+        types = Arrays.stream(types).map(t -> {
+            if (t.isPrimitive())
+                t = t.box();
+            return t;
+        }).toArray(TypeName[]::new);
+        return ParameterizedTypeName.get(clz, types);
+    }
+
+    /**
+     * Get {@link ClassName} for configuration class.
+     *
+     * @param schemaClassName Configuration schema ClassName.
+     * @return Configuration ClassName.
+     */
+    public static ClassName getConfigurationName(ClassName schemaClassName) {
+        return ClassName.get(
+            schemaClassName.packageName(),
+            schemaClassName.simpleName().replace("Schema", "Impl")
+        );
+    }
+
+    /**
+     * Get {@link ClassName} for configuration class' public interface.
+     *
+     * @param schemaClassName Configuration schema ClassName.
+     * @return Configuration's public interface ClassName.
+     */
+    public static ClassName getConfigurationInterfaceName(ClassName schemaClassName) {
+        return ClassName.get(
+            schemaClassName.packageName(),
+            schemaClassName.simpleName().replace("Schema", "")
+        );
+    }
+
+    /**
+     * Get {@link ClassName} for configuration VIEW object class.
+     *
+     * @param schemaClassName Configuration schema ClassName.
+     * @return Configuration VIEW object ClassName.
+     */
+    public static ClassName getViewName(ClassName schemaClassName) {
+        return ClassName.get(
+            schemaClassName.packageName(),
+            schemaClassName.simpleName().replace("ConfigurationSchema", "")
+        );
+    }
+
+    /**
+     * Get {@link ClassName} for configuration INIT object class.
+     *
+     * @param schemaClassName Configuration schema ClassName.
+     * @return Configuration INIT object ClassName.
+     */
+    public static ClassName getInitName(ClassName schemaClassName) {
+        return ClassName.get(
+            schemaClassName.packageName(),
+            "Init" + schemaClassName.simpleName().replace("ConfigurationSchema", "")
+        );
+    }
+
+    /**
+     * Get {@link ClassName} for configuration CHANGE object class.
+     *
+     * @param schemaClassName Configuration schema ClassName.
+     * @return Configuration CHANGE object ClassName.
+     */
+    public static ClassName getChangeName(ClassName schemaClassName) {
+        return ClassName.get(
+            schemaClassName.packageName(),
+            "Change" + schemaClassName.simpleName().replace("ConfigurationSchema", "")
+        );
+    }
+
+    /**
+     * Check whether type is {@link NamedListConfiguration}.
+     *
+     * @param type Type.
+     * @return {@code true} if type is {@link NamedListConfiguration}.
+     */
+    public static boolean isNamedConfiguration(TypeName type) {
+        if (type instanceof ParameterizedTypeName) {
+            ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) type;
+
+            if (parameterizedTypeName.rawType.equals(ClassName.get(NamedListConfiguration.class)))
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Get {@code DynamicConfiguration} inside of the named configuration.
+     *
+     * @param type Type name.
+     * @return {@link DynamicConfiguration} class name.
+     */
+    public static TypeName unwrapNamedListConfigurationClass(TypeName type) {
+        if (type instanceof ParameterizedTypeName) {
+            ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) type;
+
+            if (parameterizedTypeName.rawType.equals(ClassName.get(NamedListConfiguration.class)))
+                return parameterizedTypeName.typeArguments.get(1);
+        }
+
+        throw new ProcessorException(type + " is not a NamedListConfiguration class");
+    }
+
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ChangeClassGenerator.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ChangeClassGenerator.java
new file mode 100644
index 0000000..491485c
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ChangeClassGenerator.java
@@ -0,0 +1,87 @@
+/*
+ * 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.processor.internal.pojo;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.internal.NamedList;
+import org.apache.ignite.configuration.processor.internal.Utils;
+
+/**
+ * CHANGE object class generator.
+ */
+public class ChangeClassGenerator extends ClassGenerator {
+    /** Constructor. */
+    public ChangeClassGenerator(ProcessingEnvironment env) {
+        super(env);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected FieldMapping mapField(VariableElement field) {
+        final ConfigValue configAnnotation = field.getAnnotation(ConfigValue.class);
+        final NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
+        final Value valueAnnotation = field.getAnnotation(Value.class);
+
+        if (valueAnnotation != null && valueAnnotation.immutable())
+            return null;
+
+        final TypeMirror type = field.asType();
+        String name = field.getSimpleName().toString();
+
+        TypeName fieldType = TypeName.get(type);
+
+        if (fieldType.isPrimitive())
+            fieldType = fieldType.box();
+
+        if (namedConfigAnnotation != null || configAnnotation != null) {
+            ClassName confClass = (ClassName) fieldType;
+            fieldType = Utils.getChangeName(confClass);
+
+            if (namedConfigAnnotation != null)
+                fieldType = ParameterizedTypeName.get(ClassName.get(NamedList.class), fieldType);
+        }
+
+        final FieldSpec fieldSpec = FieldSpec.builder(fieldType, name, Modifier.PRIVATE).build();
+
+        return new FieldMapping(field, fieldSpec);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected MethodSpec mapMethod(ClassName clazz, FieldSpec field) {
+        final String name = field.name;
+        final String methodName = name.substring(0, 1).toUpperCase() + name.substring(1);
+        return MethodSpec.methodBuilder("with" + methodName)
+            .returns(clazz)
+            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+            .addParameter(field.type, name)
+            .addStatement("this.$L = $L", name, name)
+            .addStatement("return this")
+            .build();
+    }
+
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ClassGenerator.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ClassGenerator.java
new file mode 100644
index 0000000..0ec13df
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ClassGenerator.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.processor.internal.pojo;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.VariableElement;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.TypeSpec;
+import org.apache.ignite.configuration.processor.internal.Utils;
+
+/**
+ * Base POJO generator
+ */
+public abstract class ClassGenerator {
+    /** Processing environment. */
+    protected final ProcessingEnvironment env;
+
+    /** Annotation processing filer. */
+    private final Filer filer;
+
+    /** Constructor. */
+    public ClassGenerator(ProcessingEnvironment env) {
+        this.env = env;
+        this.filer = env.getFiler();
+    }
+
+    /**
+     * Create class.
+     *
+     * @param packageName Package name for class.
+     * @param className Class name.
+     * @param fields List of fields.
+     * @throws IOException If failed to write class file.
+     */
+    public final void create(String packageName, ClassName className, List<VariableElement> fields) throws IOException {
+        TypeSpec.Builder classBuilder = TypeSpec
+            .classBuilder(className)
+            .addSuperinterface(Serializable.class)
+            .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
+
+        List<FieldMapping> fieldMappings = fields.stream().map(this::mapField).filter(Objects::nonNull).collect(Collectors.toList());
+
+        generate(classBuilder, packageName, className, fieldMappings);
+
+        final TypeSpec viewClass = classBuilder.build();
+        JavaFile classFile = JavaFile.builder(packageName, viewClass).build();
+        classFile.writeTo(filer);
+    }
+
+    /**
+     * Generate class fields, methods and constructor.
+     *
+     * @param classBuilder Class builder.
+     * @param packageName Package name.
+     * @param className Class name.
+     * @param fieldMappings Fields' mappings.
+     */
+    protected void generate(TypeSpec.Builder classBuilder, String packageName, ClassName className, List<FieldMapping> fieldMappings) {
+        List<FieldSpec> fieldSpecs = fieldMappings.stream().map(FieldMapping::getFieldSpec).collect(Collectors.toList());
+
+        List<MethodSpec> methodSpecs = fieldSpecs.stream().map(field -> mapMethod(className, field)).filter(Objects::nonNull).collect(Collectors.toList());
+
+        classBuilder.addFields(fieldSpecs);
+        classBuilder.addMethods(methodSpecs);
+
+        final MethodSpec constructor = createConstructor(fieldSpecs);
+        if (constructor != null)
+            classBuilder.addMethod(constructor);
+
+        final List<MethodSpec> getters = Utils.createGetters(fieldSpecs);
+
+        classBuilder.addMethods(getters);
+    }
+
+    /**
+     * Create {@link FieldSpec} from {@link VariableElement}.
+     *
+     * @param field Configuration class field.
+     * @return Mapping.
+     */
+    protected abstract FieldMapping mapField(VariableElement field);
+
+    /**
+     * Create access methods for field.
+     *
+     * @param clazz Method return type.
+     * @param field Configuration class field.
+     * @return Method specs.
+     */
+    protected abstract MethodSpec mapMethod(ClassName clazz, FieldSpec field);
+
+    /**
+     * Create constructor from fields.
+     *
+     * @param fields Configuration fields.
+     * @return If null, constructor won't be created.
+     */
+    protected MethodSpec createConstructor(List<FieldSpec> fields) {
+        return null;
+    }
+
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/FieldMapping.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/FieldMapping.java
new file mode 100644
index 0000000..c0b216e
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/FieldMapping.java
@@ -0,0 +1,52 @@
+/*
+ * 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.processor.internal.pojo;
+
+import javax.lang.model.element.VariableElement;
+import com.squareup.javapoet.FieldSpec;
+
+/**
+ * Mapping from {@link VariableElement} to {@link FieldSpec}
+ */
+public class FieldMapping {
+    /** Variable element. */
+    private final VariableElement variableElement;
+
+    /** Field spec. */
+    private final FieldSpec fieldSpec;
+
+    /** Constructor. */
+    public FieldMapping(VariableElement variableElement, FieldSpec fieldSpec) {
+        this.variableElement = variableElement;
+        this.fieldSpec = fieldSpec;
+    }
+
+    /**
+     * Get varaible element.
+     */
+    public VariableElement getVariableElement() {
+        return variableElement;
+    }
+
+    /**
+     * Get field spec.
+     */
+    public FieldSpec getFieldSpec() {
+        return fieldSpec;
+    }
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/InitClassGenerator.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/InitClassGenerator.java
new file mode 100644
index 0000000..6f5e784
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/InitClassGenerator.java
@@ -0,0 +1,83 @@
+/*
+ * 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.processor.internal.pojo;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.internal.NamedList;
+import org.apache.ignite.configuration.processor.internal.Utils;
+
+/**
+ * INIT object class generator.
+ */
+public class InitClassGenerator extends ClassGenerator {
+    /** Constructor. */
+    public InitClassGenerator(ProcessingEnvironment env) {
+        super(env);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected FieldMapping mapField(VariableElement field) {
+        final ConfigValue configAnnotation = field.getAnnotation(ConfigValue.class);
+        final NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
+
+        final TypeMirror type = field.asType();
+        String name = field.getSimpleName().toString();
+
+        TypeName fieldType = TypeName.get(type);
+
+        if (fieldType.isPrimitive())
+            fieldType = fieldType.box();
+
+        if (namedConfigAnnotation != null || configAnnotation != null) {
+            ClassName confClass = (ClassName) fieldType;
+            fieldType = Utils.getInitName(confClass);
+            
+            if (namedConfigAnnotation != null)
+                fieldType = ParameterizedTypeName.get(ClassName.get(NamedList.class), fieldType);
+
+        }
+
+        final FieldSpec fieldSpec = FieldSpec.builder(fieldType, name, Modifier.PRIVATE).build();
+
+        return new FieldMapping(field, fieldSpec);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected MethodSpec mapMethod(ClassName clazz, FieldSpec field) {
+        final String name = field.name;
+        final String methodName = name.substring(0, 1).toUpperCase() + name.substring(1);
+        return MethodSpec.methodBuilder("with" + methodName)
+            .returns(clazz)
+            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+            .addParameter(field.type, name)
+            .addStatement("this.$L = $L", name, name)
+            .addStatement("return this")
+            .build();
+    }
+
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ViewClassGenerator.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ViewClassGenerator.java
new file mode 100644
index 0000000..a192a04
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/pojo/ViewClassGenerator.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.processor.internal.pojo;
+
+import java.util.List;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.internal.NamedList;
+import org.apache.ignite.configuration.processor.internal.Utils;
+
+/**
+ * VIEW object class generator.
+ */
+public class ViewClassGenerator extends ClassGenerator {
+    /** Constructor. */
+    public ViewClassGenerator(ProcessingEnvironment env) {
+        super(env);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected FieldMapping mapField(VariableElement field) {
+        final ConfigValue configAnnotation = field.getAnnotation(ConfigValue.class);
+        final NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
+
+        final TypeMirror type = field.asType();
+        String name = field.getSimpleName().toString();
+
+        TypeName fieldType = TypeName.get(type);
+
+        if (fieldType.isPrimitive())
+            fieldType = fieldType.box();
+
+        if (namedConfigAnnotation != null || configAnnotation != null) {
+            ClassName confClass = (ClassName) fieldType;
+            fieldType = Utils.getViewName(confClass);
+
+            if (namedConfigAnnotation != null)
+                fieldType = ParameterizedTypeName.get(ClassName.get(NamedList.class), fieldType);
+        }
+
+        final FieldSpec fieldSpec = FieldSpec.builder(fieldType, name, Modifier.PRIVATE, Modifier.FINAL).build();
+
+        return new FieldMapping(field, fieldSpec);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected MethodSpec mapMethod(ClassName clazz, FieldSpec field) {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected MethodSpec createConstructor(List<FieldSpec> fields) {
+        return Utils.createConstructor(fields);
+    }
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/validation/ValidationGenerator.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/validation/ValidationGenerator.java
new file mode 100644
index 0000000..6b3cc44
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/configuration/processor/internal/validation/ValidationGenerator.java
@@ -0,0 +1,174 @@
+/*
+ * 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.processor.internal.validation;
+
+import com.squareup.javapoet.CodeBlock;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.MirroredTypesException;
+import javax.lang.model.type.TypeMirror;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import org.apache.ignite.configuration.internal.DynamicConfiguration;
+import org.apache.ignite.configuration.annotation.Validate;
+import org.apache.ignite.configuration.internal.validation.MaxValidator;
+import org.apache.ignite.configuration.internal.validation.MinValidator;
+import org.apache.ignite.configuration.internal.validation.NotNullValidator;
+import org.apache.ignite.configuration.processor.internal.ProcessorException;
+
+/**
+ * Class that handles validation generation.
+ */
+public class ValidationGenerator {
+    /**
+     * Private constructor.
+     */
+    private ValidationGenerator() {
+    }
+
+    /**
+     * Generate validation block.
+     *
+     * @param variableElement Configuration field.
+     * @return Code block for field validation.
+     */
+    public static CodeBlock generateValidators(VariableElement variableElement) {
+        List<CodeBlock> validators = new ArrayList<>();
+
+        processMin(variableElement, validators);
+
+        processMax(variableElement, validators);
+
+        processNotNull(variableElement, validators);
+
+        processCustomValidations(variableElement, validators);
+
+        String text = validators.stream().map(v -> "$L").collect(Collectors.joining(","));
+
+        final CodeBlock validatorsArguments = CodeBlock.builder().add(text, validators.toArray()).build();
+
+        return CodeBlock.builder().add("$T.asList($L)", Arrays.class, validatorsArguments).build();
+    }
+
+    /**
+     * Process {@link Min} annotations.
+     * @param variableElement Field.
+     * @param validators Validators code blocks.
+     */
+    private static void processMin(VariableElement variableElement, List<CodeBlock> validators) {
+        final Min minAnnotation = variableElement.getAnnotation(Min.class);
+        if (minAnnotation != null) {
+            final long minValue = minAnnotation.value();
+            final String message = minAnnotation.message();
+            final CodeBlock build = CodeBlock.builder().add(
+                "new $T<$T<?, ?, ?>>($L, $S)", MinValidator.class, DynamicConfiguration.class, minValue, message
+            ).build();
+            validators.add(build);
+        }
+    }
+
+    /**
+     * Process {@link Max} annotations.
+     * @param variableElement Field.
+     * @param validators Validators code blocks.
+     */
+    private static void processMax(VariableElement variableElement, List<CodeBlock> validators) {
+        final Max maxAnnotation = variableElement.getAnnotation(Max.class);
+
+        if (maxAnnotation != null) {
+            final long maxValue = maxAnnotation.value();
+            final String message = maxAnnotation.message();
+
+            // new MaxValidator
+            final CodeBlock build = CodeBlock.builder().add(
+                "new $T<$T<?, ?, ?>>($L, $S)", MaxValidator.class, DynamicConfiguration.class, maxValue, message
+            ).build();
+
+            validators.add(build);
+        }
+    }
+
+    /**
+     * Process {@link NotNull} annotation.
+     * @param variableElement Field.
+     * @param validators Validators code blocks.
+     */
+    private static void processNotNull(VariableElement variableElement, List<CodeBlock> validators) {
+        final NotNull notNull = variableElement.getAnnotation(NotNull.class);
+
+        if (notNull != null) {
+            final String message = notNull.message();
+
+            final CodeBlock build = CodeBlock.builder().add(
+                "new $T<$T<?, ?, ?>>($S)", NotNullValidator.class, DynamicConfiguration.class, message
+            ).build();
+
+            validators.add(build);
+        }
+    }
+
+    /**
+     * Process custom validations from {@link Validate} annotation.
+     * @param variableElement Field.
+     * @param validators Validators code blocks.
+     */
+    private static void processCustomValidations(VariableElement variableElement, List<CodeBlock> validators) {
+        List<Validate> validateAnnotations = new ArrayList<>();
+
+        // There can repeatable Validate annotation, hence Validate.List
+        final Validate.List validateAnnotationsList = variableElement.getAnnotation(Validate.List.class);
+
+        if (validateAnnotationsList != null)
+            validateAnnotations.addAll(Arrays.asList(validateAnnotationsList.value()));
+
+        // Or there is single Validate annotation
+        final Validate validateAnnotationSingle = variableElement.getAnnotation(Validate.class);
+
+        if (validateAnnotationSingle != null)
+            validateAnnotations.add(validateAnnotationSingle);
+
+        for (Validate validateAnnotation : validateAnnotations) {
+            List<? extends TypeMirror> values = null;
+            try {
+                //  From JavaDocs: The annotation returned by this method could contain an element whose value is of type Class.
+                //  This value cannot be returned directly: information necessary to locate and load a class
+                //  (such as the class loader to use) is not available, and the class might not be loadable at all.
+                //  Attempting to read a Class object by invoking the relevant method on the returned annotation will
+                //  result in a MirroredTypeException, from which the corresponding TypeMirror may be extracted.
+                //  Similarly, attempting to read a Class[]-valued element will result in a MirroredTypesException.
+                validateAnnotation.value();
+            } catch (MirroredTypesException e) {
+                values = e.getTypeMirrors();
+            }
+
+            if (values == null)
+                throw new ProcessorException("Failed to retrieve Validate annotation value");
+
+            for (TypeMirror value : values) {
+                final String message = validateAnnotation.message();
+                final CodeBlock build = CodeBlock.builder().add("new $T($S)", value, message).build();
+                validators.add(build);
+            }
+        }
+    }
+
+}
diff --git a/modules/configuration-annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/modules/configuration-annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 0000000..cd4d259
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@ -0,0 +1 @@
+org.apache.ignite.configuration.processor.internal.Processor
\ No newline at end of file
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/AbstractProcessorTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/AbstractProcessorTest.java
new file mode 100644
index 0000000..a089a50
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/AbstractProcessorTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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.processor.internal;
+
+import com.google.common.base.Functions;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import com.squareup.javapoet.ClassName;
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import javax.tools.JavaFileObject;
+
+import static com.google.testing.compile.Compiler.javac;
+
+/**
+ * Base class for configuration annotation processor tests.
+ */
+public class AbstractProcessorTest {
+
+    /**
+     * Compile set of classes
+     * @param schemaClasses Configuration schema classes.
+     * @return Result of batch compilation.
+     */
+    protected static BatchCompilation batchCompile(ClassName... schemaClasses) {
+        List<String> fileNames = Arrays.stream(schemaClasses)
+            .map(name -> {
+                final String folderName = name.packageName().replaceAll("\\.", File.separator);
+                return String.format("%s%c%s.java", folderName, File.separatorChar, name.simpleName());
+            })
+            .collect(Collectors.toList());
+
+        final List<JavaFileObject> fileObjects = fileNames.stream().map(JavaFileObjects::forResource).collect(Collectors.toList());
+
+        final Compilation compilation = javac()
+            .withProcessors(new Processor())
+            .compile(fileObjects);
+
+        return new BatchCompilation(Arrays.asList(schemaClasses), compilation);
+    }
+
+    /**
+     * Get {@link ConfigSet} object from generated classes.
+     * @param clazz Configuration schema ClassName.
+     * @param generatedClasses Map with all generated classes.
+     * @return ConfigSet.
+     */
+    protected static ConfigSet getConfigSet(ClassName clazz, final Map<ClassName, JavaFileObject> generatedClasses) {
+        final ClassName configurationName = Utils.getConfigurationName(clazz);
+        final ClassName viewName = Utils.getViewName(clazz);
+        final ClassName initName = Utils.getInitName(clazz);
+        final ClassName changeName = Utils.getChangeName(clazz);
+
+        final JavaFileObject configurationFileObject = generatedClasses.get(configurationName);
+        final JavaFileObject viewClass = generatedClasses.get(viewName);
+        final JavaFileObject initClass = generatedClasses.get(initName);
+        final JavaFileObject changeClass = generatedClasses.get(changeName);
+
+        return new ConfigSet(configurationFileObject, viewClass, initClass, changeClass);
+    }
+
+    /**
+     * Get {@link ClassName} object from generated file path.
+     * @param fileName File path.
+     * @return ClassName.
+     */
+    protected static ClassName fromGeneratedFilePath(String fileName) {
+        return fromFilePath(fileName.replace("/SOURCE_OUTPUT/", ""));
+    }
+
+    /**
+     * Get {@link ClassName} object from file path.
+     * @param fileName File path.
+     * @return ClassName.
+     */
+    protected static ClassName fromFilePath(String fileName) {
+        int slashIdx = fileName.lastIndexOf("/");
+        int dotJavaIdx = fileName.lastIndexOf(".java");
+
+        String packageName = fileName.substring(0, slashIdx).replaceAll("/", ".");
+
+        final String className = fileName.substring(slashIdx + 1, dotJavaIdx);
+
+        return ClassName.get(packageName, className);
+    }
+
+    /**
+     * Result of multiple compiled schema classes.
+     */
+    protected static class BatchCompilation {
+        /** Generated source files. */
+        private final List<JavaFileObject> generatedSources;
+
+        /** Generated classes mapped by config schema class name. */
+        private final Map<ClassName, JavaFileObject> generatedClasses;
+
+        /** Config class sets by config schema class name. */
+        private final Map<ClassName, ConfigSet> classSets;
+
+        /** Compilation status. */
+        private final Compilation compilationStatus;
+
+        /**
+         * Constructor.
+         * @param schemaClasses List of schema class names.
+         * @param compilation Compilation status.
+         */
+        public BatchCompilation(List<ClassName> schemaClasses, Compilation compilation) {
+            this.compilationStatus = compilation;
+
+            generatedSources = compilation.generatedSourceFiles();
+
+            generatedClasses = generatedSources.stream()
+                .collect(Collectors.toMap(object -> fromGeneratedFilePath(object.getName()), Functions.identity()));
+
+            classSets = schemaClasses.stream().collect(
+                Collectors.toMap(name -> name, name -> getConfigSet(name, generatedClasses))
+            );
+        }
+
+        /**
+         * Get config class set by schema class name.
+         * @param schemaClass Schema class name.
+         * @return Config class set.
+         */
+        public ConfigSet getBySchema(ClassName schemaClass) {
+            return classSets.get(schemaClass);
+        }
+
+        /**
+         * Get compilation status.
+         * @return Compilation status.
+         */
+        public Compilation getCompilationStatus() {
+            return compilationStatus;
+        }
+
+        /**
+         * Get all generated source files.
+         * @return Generated source files.
+         */
+        public List<JavaFileObject> generated() {
+            return generatedSources;
+        }
+    }
+
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ConfigSet.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ConfigSet.java
new file mode 100644
index 0000000..c5966ab
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ConfigSet.java
@@ -0,0 +1,123 @@
+/*
+ * 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.processor.internal;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import javax.tools.JavaFileObject;
+import org.apache.commons.io.IOUtils;
+import spoon.Launcher;
+
+/**
+ * Wrapper for generated classes of the configuration schema.
+ */
+public class ConfigSet {
+    /** Configuration class. */
+    private final JavaFileObject configurationClass;
+
+    /** VIEW class. */
+    private final JavaFileObject viewClass;
+
+    /** INIT class. */
+    private final JavaFileObject initClass;
+
+    /** CHANGE class. */
+    private final JavaFileObject changeClass;
+
+    /** Parsed configuration class. */
+    private final ParsedClass conf;
+
+    /** Parsed VIEW class. */
+    private final ParsedClass view;
+
+    /** Parsed INIT class. */
+    private final ParsedClass init;
+
+    /** Parsed CHANGE class. */
+    private final ParsedClass change;
+
+    /** Constructor. */
+    public ConfigSet(JavaFileObject configurationClass, JavaFileObject viewClass, JavaFileObject initClass, JavaFileObject changeClass) {
+        this.configurationClass = configurationClass;
+        this.viewClass = viewClass;
+        this.initClass = initClass;
+        this.changeClass = changeClass;
+
+        if (configurationClass != null)
+            this.conf = parse(configurationClass);
+        else
+            this.conf = null;
+
+        if (viewClass != null)
+            this.view = parse(viewClass);
+        else
+            this.view = null;
+
+        if (initClass != null)
+            this.init = parse(initClass);
+        else
+            this.init = null;
+
+        if (changeClass != null)
+            this.change = parse(changeClass);
+        else
+            this.change = null;
+    }
+
+    /**
+     * Parse source file.
+     * @param clz Class file object.
+     * @return Parsed class.
+     */
+    private ParsedClass parse(JavaFileObject clz) {
+        String classFileContent;
+        try {
+            classFileContent = IOUtils.toString(clz.openInputStream(), StandardCharsets.UTF_8);
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to parse class: " + e.getMessage(), e);
+        }
+
+        return new ParsedClass(Launcher.parseClass(classFileContent));
+    }
+
+    /**
+     * @return {@code true} if all required classes were generated.
+     */
+    public boolean allGenerated() {
+        return configurationClass != null && viewClass != null && initClass != null && changeClass != null;
+    }
+
+    /** */
+    public ParsedClass getConfigurationClass() {
+        return conf;
+    }
+
+    /** */
+    public ParsedClass getViewClass() {
+        return view;
+    }
+
+    /** */
+    public ParsedClass getInitClass() {
+        return init;
+    }
+
+    /** */
+    public ParsedClass getChangeClass() {
+        return change;
+    }
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/HasFieldMatcher.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/HasFieldMatcher.java
new file mode 100644
index 0000000..7b729fd
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/HasFieldMatcher.java
@@ -0,0 +1,129 @@
+/*
+ * 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.processor.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import spoon.reflect.reference.CtFieldReference;
+
+/**
+ * Hamcrest matcher that tests class for field existence.
+ */
+public class HasFieldMatcher extends BaseMatcher<ParsedClass> {
+    /** Name of the expected field. */
+    private String fieldName;
+
+    /** Type of the expected field. */
+    private String fieldType;
+
+    /**
+     * Constructor.
+     * @param fieldName Name of the expected field.
+     * @param fieldType Type of the expected field.
+     */
+    private HasFieldMatcher(String fieldName, String fieldType) {
+        this.fieldName = fieldName;
+        this.fieldType = fieldType;
+    }
+
+    /**
+     * Create matcher for fields with types.
+     * @param arguments Array of field names and field types, paired.
+     * @return Matcher.
+     */
+    public static BaseMatcher<ParsedClass> hasFields(String... arguments) {
+        if (arguments.length % 2 != 0)
+            throw new RuntimeException("Number of field names should be equal to number of field types");
+
+        List<HasFieldMatcher> matcherList = new ArrayList<>();
+
+        for (int i = 0; i < arguments.length; i+=2)
+            matcherList.add(new HasFieldMatcher(arguments[i], arguments[i + 1]));
+
+        return new BaseMatcher<ParsedClass>() {
+            /** Currently used matcher. */
+            int currentMatcher = 0;
+
+            /** {@inheritDoc} */
+            @Override public void describeTo(Description description) {
+                matcherList.get(currentMatcher).describeTo(description);
+            }
+
+            /** {@inheritDoc} */
+            @Override public void describeMismatch(Object item, Description description) {
+                matcherList.get(currentMatcher).describeMismatch(item, description);
+            }
+
+            /** {@inheritDoc} */
+            @Override public boolean matches(Object o) {
+                for (int i = 0; i < matcherList.size(); i++) {
+                    currentMatcher = i;
+                    if (!matcherList.get(i).matches(o))
+                        return false;
+                }
+
+                return true;
+            }
+        };
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean matches(Object o) {
+        if (!(o instanceof ParsedClass))
+            return false;
+
+        ParsedClass cls = (ParsedClass) o;
+
+        final Map<String, CtFieldReference<?>> fields = cls.getFields();
+        final CtFieldReference<?> field = fields.get(fieldName);
+
+        if (field == null)
+            return false;
+
+        return field.getType().getQualifiedName().equals(fieldType);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void describeTo(Description description) {
+        description.appendText(String.format("has field \"%s\" with type \"%s\"", fieldName, fieldType));
+    }
+
+    /** {@inheritDoc} */
+    @Override public void describeMismatch(Object item, Description description) {
+        if (!(item instanceof ParsedClass)) {
+            description.appendText("is not ParsedClass instance");
+            return;
+        }
+
+        ParsedClass cls = (ParsedClass) item;
+
+        final Map<String, CtFieldReference<?>> fields = cls.getFields();
+        final CtFieldReference<?> field = fields.get(fieldName);
+
+        if (field == null) {
+            description.appendText("doesn't have field \"" + fieldName + "\"");
+            return;
+        }
+
+        final String actualFieldType = field.getType().getQualifiedName();
+        if (!actualFieldType.equals(fieldType))
+            description.appendText(String.format("\"%s\" has incorrect type \"%s\"", fieldName, actualFieldType));
+    }
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/HasMethodMatcher.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/HasMethodMatcher.java
new file mode 100644
index 0000000..385a280
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/HasMethodMatcher.java
@@ -0,0 +1,126 @@
+/*
+ * 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.processor.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import spoon.reflect.declaration.CtMethod;
+
+/**
+ * Hamcrest matcher that tests class for method existence.
+ */
+public class HasMethodMatcher extends BaseMatcher<ParsedClass> {
+    /** Name of the expected field. */
+    private String methodName;
+
+    /** Type of the expected field. */
+    private String methodReturnType;
+
+    /** Constructor. */
+    public HasMethodMatcher(String methodName, String methodReturnType) {
+        this.methodName = methodName;
+        this.methodReturnType = methodReturnType;
+    }
+
+    /**
+     * Create matcher for fields with types.
+     * @param arguments Array of field names and field types, paired.
+     * @return Matcher.
+     */
+    public static BaseMatcher<ParsedClass> hasMethods(String... arguments) {
+        if (arguments.length % 2 != 0)
+            throw new RuntimeException("Number of method names should be equal to number of method return types");
+
+        List<HasMethodMatcher> matcherList = new ArrayList<>();
+
+        for (int i = 0; i < arguments.length; i+=2)
+            matcherList.add(new HasMethodMatcher(arguments[i], arguments[i + 1]));
+
+        return new BaseMatcher<ParsedClass>() {
+            /** Currently used matcher. */
+            int currentMatcher = 0;
+
+            /** {@inheritDoc} */
+            @Override public void describeTo(Description description) {
+                matcherList.get(currentMatcher).describeTo(description);
+            }
+
+            /** {@inheritDoc} */
+            @Override public void describeMismatch(Object item, Description description) {
+                matcherList.get(currentMatcher).describeMismatch(item, description);
+            }
+
+            /** {@inheritDoc} */
+            @Override public boolean matches(Object o) {
+                for (int i = 0; i < matcherList.size(); i++) {
+                    currentMatcher = i;
+                    if (!matcherList.get(i).matches(o))
+                        return false;
+                }
+
+                return true;
+            }
+        };
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean matches(Object o) {
+        if (!(o instanceof ParsedClass))
+            return false;
+
+        ParsedClass cls = (ParsedClass) o;
+
+        final Map<String, CtMethod<?>> methods = cls.getMethods();
+        final CtMethod<?> method = methods.get(methodName);
+
+        if (method == null)
+            return false;
+
+        return method.getType().getQualifiedName().equals(methodReturnType);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void describeTo(Description description) {
+        description.appendText(String.format("has method \"%s\" with return type \"%s\"", methodName, methodReturnType));
+    }
+
+    /** {@inheritDoc} */
+    @Override public void describeMismatch(Object item, Description description) {
+        if (!(item instanceof ParsedClass)) {
+            description.appendText("is not ParsedClass instance");
+            return;
+        }
+
+        ParsedClass cls = (ParsedClass) item;
+
+        final Map<String, CtMethod<?>> methods = cls.getMethods();
+        final CtMethod<?> method = methods.get(methodName);
+
+        if (method == null) {
+            description.appendText("doesn't have method \"" + methodName + "\"");
+            return;
+        }
+
+        final String actualMethodReturnType = method.getType().getQualifiedName();
+
+        if (!actualMethodReturnType.equals(methodReturnType))
+            description.appendText(String.format("\"%s\" has incorrect return type \"%s\"", methodName, actualMethodReturnType));
+    }
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ParsedClass.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ParsedClass.java
new file mode 100644
index 0000000..15502b7
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ParsedClass.java
@@ -0,0 +1,107 @@
+/*
+ * 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.processor.internal;
+
+import com.google.common.base.Functions;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import spoon.reflect.declaration.CtClass;
+import spoon.reflect.declaration.CtConstructor;
+import spoon.reflect.declaration.CtExecutable;
+import spoon.reflect.declaration.CtMethod;
+import spoon.reflect.declaration.CtParameter;
+import spoon.reflect.reference.CtFieldReference;
+import spoon.reflect.reference.CtReference;
+
+/**
+ * Convenient wrapper for parsed source file.
+ * Method and constructor signatures are represented by string containing name and list of argument types.
+ * E.g.: "foo(java.lang.Integer, java.lang.String)" for method {@code public Object foo(Integer a, String b)}.
+ */
+public class ParsedClass {
+    /** Class info. */
+    private final CtClass<?> cls;
+
+    /** Class fields by name. */
+    private final Map<String, CtFieldReference<?>> fields;
+
+    /** Class methods by signature. */
+    private final Map<String, CtMethod<?>> methods;
+
+    /** Class constructors by signature. */
+    private final Map<String, CtConstructor<?>> constructors;
+
+    /**
+     * Constructor.
+     * @param cls Class info.
+     */
+    public ParsedClass(CtClass<?> cls) {
+        this.cls = cls;
+
+        this.fields = cls.getAllFields()
+            .stream()
+            .collect(Collectors.toMap(CtReference::getSimpleName, Functions.identity()));
+
+        this.methods = cls.getMethods()
+            .stream()
+            .collect(Collectors.toMap(this::getMethodName, Functions.identity()));
+
+        this.constructors = cls.getConstructors()
+            .stream()
+            .collect(Collectors.toMap(this::getMethodName, Functions.identity()));
+    }
+
+    /**
+     * Get method name from method meta info.
+     * @param method Method meta info.
+     * @return Method name.
+     */
+    private String getMethodName(CtExecutable<?> method) {
+        final List<CtParameter<?>> parameters = method.getParameters();
+
+        final String params = parameters.stream().map(parameter ->
+            parameter.getType().getQualifiedName()).collect(Collectors.joining(", ")
+        );
+
+        return method.getSimpleName() + "(" + params + ")";
+    }
+
+    /**
+     * Get fields.
+     * @return Fields.
+     */
+    public Map<String, CtFieldReference<?>> getFields() {
+        return fields;
+    }
+
+    /**
+     * Get methods.
+     * @return Methods.
+     */
+    public Map<String, CtMethod<?>> getMethods() {
+        return methods;
+    }
+
+    /**
+     * Get constructors.
+     * @return Constructors.
+     */
+    public Map<String, CtConstructor<?>> getConstructors() {
+        return constructors;
+    }
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ProcessorTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ProcessorTest.java
new file mode 100644
index 0000000..dc361dc
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/ProcessorTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.processor.internal;
+
+import com.google.testing.compile.CompilationSubject;
+import com.squareup.javapoet.ClassName;
+import org.hamcrest.MatcherAssert;
+import org.junit.Test;
+
+import static org.apache.ignite.configuration.processor.internal.HasFieldMatcher.hasFields;
+import static org.apache.ignite.configuration.processor.internal.HasMethodMatcher.hasMethods;
+import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test for basic code generation scenarios.
+ */
+public class ProcessorTest extends AbstractProcessorTest {
+    /**
+     * The simplest test for code generation.
+     */
+    @Test
+    public void test() {
+        final String packageName = "org.apache.ignite.configuration.processor.internal";
+
+        final ClassName testConfigurationSchema = ClassName.get(packageName, "TestConfigurationSchema");
+
+        final BatchCompilation batch = batchCompile(testConfigurationSchema);
+
+        CompilationSubject.assertThat(batch.getCompilationStatus()).succeeded();
+
+        assertEquals(7, batch.generated().size());
+
+        final ConfigSet classSet = batch.getBySchema(testConfigurationSchema);
+
+        assertTrue(classSet.allGenerated());
+
+        MatcherAssert.assertThat(
+            classSet.getViewClass(),
+            hasFields(
+                "value1", Types.STRING,
+                "primitiveLong", Types.LONG,
+                "boxedLong", Types.LONG,
+                "primitiveInt", Types.INT,
+                "boxedInt", Types.INT
+            )
+        );
+
+        MatcherAssert.assertThat(
+            classSet.getViewClass(),
+            hasMethods(
+                "value1()", Types.STRING,
+                "primitiveLong()", Types.LONG,
+                "boxedLong()", Types.LONG,
+                "primitiveInt()", Types.INT,
+                "boxedInt()", Types.INT
+            )
+        );
+
+        MatcherAssert.assertThat(
+            classSet.getInitClass(),
+            hasFields(
+                "value1", Types.STRING,
+                "primitiveLong", Types.LONG,
+                "boxedLong", Types.LONG,
+                "primitiveInt", Types.INT,
+                "boxedInt", Types.INT
+            )
+        );
+
+        String initTypeName = Types.typeName(packageName, "InitTest");
+        
+        MatcherAssert.assertThat(
+            classSet.getInitClass(),
+            hasMethods(
+                "value1()", Types.STRING,
+                "primitiveLong()", Types.LONG,
+                "boxedLong()", Types.LONG,
+                "primitiveInt()", Types.INT,
+                "boxedInt()", Types.INT,
+                "withValue1(java.lang.String)", initTypeName,
+                "withPrimitiveLong(java.lang.Long)", initTypeName,
+                "withBoxedLong(java.lang.Long)", initTypeName,
+                "withPrimitiveInt(java.lang.Integer)", initTypeName,
+                "withBoxedInt(java.lang.Integer)", initTypeName
+            )
+        );
+    }
+
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/Types.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/Types.java
new file mode 100644
index 0000000..0d42050
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/processor/internal/Types.java
@@ -0,0 +1,32 @@
+package org.apache.ignite.configuration.processor.internal;
+
+/**
+ * Type names for testing.
+ */
+public class Types {
+    /** Java lang package name. */
+    private static final String PKG_JAVA_LANG = "java.lang.";
+
+    /** Integer. */
+    public static final String INT = PKG_JAVA_LANG + "Integer";
+
+    /** Long. */
+    public static final String LONG = PKG_JAVA_LANG + "Long";
+
+    /** String. */
+    public static final String STRING = PKG_JAVA_LANG + "String";
+
+    /** Double. */
+    public static final String DOUBLE = PKG_JAVA_LANG + "Double";
+
+    /**
+     * Get type name by package name and class name.
+     * @param packageName Package name.
+     * @param className Class name.
+     * @return Type name.
+     */
+    public static String typeName(String packageName, String className) {
+        return String.format("%s.%s", packageName, className);
+    }
+
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/AutoAdjustConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/AutoAdjustConfigurationSchema.java
new file mode 100644
index 0000000..ef3c02b
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/AutoAdjustConfigurationSchema.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.sample;
+
+import javax.validation.constraints.Min;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.Validate;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.sample.validation.AutoAdjustValidator;
+import org.apache.ignite.configuration.sample.validation.AutoAdjustValidator2;
+
+/**
+ * Test auto adjust configuration schema.
+ */
+@Config
+public class AutoAdjustConfigurationSchema {
+    /** Timeout. */
+    @Value
+    @Min(value = 0, message = "Minimal is 0")
+    @Validate(value = AutoAdjustValidator.class, message = "a")
+    @Validate(value = AutoAdjustValidator2.class, message = "b")
+    private long timeout;
+
+    /** Enabled. */
+    @Value
+    private boolean enabled;
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/BaselineConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/BaselineConfigurationSchema.java
new file mode 100644
index 0000000..bc6e314
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/BaselineConfigurationSchema.java
@@ -0,0 +1,37 @@
+/*
+ * 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.sample;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+
+/**
+ * Test baseline configuration schema.
+ */
+@Config
+public class BaselineConfigurationSchema {
+    /** Auto adjust */
+    @ConfigValue
+    private AutoAdjustConfigurationSchema autoAdjust;
+
+    /** Nodes. */
+    @NamedConfigValue
+    private NodeConfigurationSchema nodes;
+
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/CacheConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/CacheConfigurationSchema.java
new file mode 100644
index 0000000..e6da55b
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/CacheConfigurationSchema.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.configuration.sample;
+
+import javax.validation.constraints.Min;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.Value;
+/**
+ * Test cache configuration schema.
+ */
+@Config
+public class CacheConfigurationSchema {
+    /** Size. */
+    @Value
+    @Min(value = 1, message = "Minimal cache size is 1")
+    private int size;
+
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ClusterWideConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ClusterWideConfigurationSchema.java
new file mode 100644
index 0000000..2c51d25
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/ClusterWideConfigurationSchema.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.configuration.sample;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.NamedConfigValue;
+/**
+ * Test cluster wide configuration schema.
+ */
+@Config(value = "cluster", root = true)
+public class ClusterWideConfigurationSchema {
+    /** Cache. */
+    @NamedConfigValue
+    CacheConfigurationSchema cacheConfig;
+
+    /** Baseline. */
+    @ConfigValue
+    private BaselineConfigurationSchema baseline;
+
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/LocalConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/LocalConfigurationSchema.java
new file mode 100644
index 0000000..6f6cf7f
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/LocalConfigurationSchema.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.sample;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+
+/**
+ * Test local configuration schema.
+ */
+@Config(value = "local", root = true)
+public class LocalConfigurationSchema {
+    /** Baseline. */
+    @ConfigValue
+    private BaselineConfigurationSchema baseline;
+
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/NodeConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/NodeConfigurationSchema.java
new file mode 100644
index 0000000..74d2d48
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/NodeConfigurationSchema.java
@@ -0,0 +1,45 @@
+/*
+ * 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.sample;
+
+import javax.validation.constraints.NotNull;
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.Validate;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.sample.validation.NodeValidator;
+
+/**
+ * Test node configuration schema.
+ */
+@Config
+public class NodeConfigurationSchema {
+    /** Consistent id. */
+    @Value(immutable = true)
+    @NotNull(message = "Consistent id must not be null")
+    private String consistentId;
+
+    /** Port. */
+    @Value
+    private int port;
+
+    /** Auto adjust enabled. */
+    @Value
+    @Validate(NodeValidator.class)
+    private boolean autoAdjustEnabled;
+
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java
new file mode 100644
index 0000000..d200efc
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/UsageTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.sample;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.function.Consumer;
+import org.apache.ignite.configuration.Configurator;
+import org.apache.ignite.configuration.internal.NamedList;
+import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.apache.ignite.configuration.PublicConfigurator;
+
+/**
+ * Simple usage test of generated configuration schema.
+ */
+public class UsageTest {
+
+    /**
+     * Test creation of configuration and calling configuration API methods.
+     */
+    @Test
+    public void test() {
+        final ConfigurationStorage storage = new ConfigurationStorage() {
+
+            @Override
+            public <T extends Serializable> void save(String propertyName, T object) {
+
+            }
+
+            @Override
+            public <T extends Serializable> T get(String propertyName) {
+                return null;
+            }
+
+            @Override
+            public <T extends Serializable> void listen(String key, Consumer<T> listener) {
+
+            }
+        };
+
+        InitLocal initLocal = new InitLocal().withBaseline(
+            new InitBaseline()
+                .withNodes(
+                    new NamedList<>(
+                        Collections.singletonMap("node1", new InitNode().withConsistentId("test").withPort(1000))
+                    )
+                )
+                .withAutoAdjust(new InitAutoAdjust().withEnabled(true).withTimeout(100000L))
+        );
+
+        final Configurator<LocalConfigurationImpl> configurator = Configurator.create(
+            storage,
+            LocalConfigurationImpl::new,
+            initLocal
+        );
+
+        final LocalConfiguration root = configurator.getRoot();
+        root.baseline().autoAdjust().enabled().value();
+
+        try {
+            configurator.set(Selectors.LOCAL_BASELINE_AUTO_ADJUST_ENABLED, false);
+            Assertions.fail();
+        } catch (ConfigurationValidationException e) {}
+        configurator.set(Selectors.LOCAL_BASELINE_AUTO_ADJUST, new ChangeAutoAdjust().withEnabled(false).withTimeout(0L));
+        configurator.getRoot().baseline().nodes().get("node1").autoAdjustEnabled(false);
+        configurator.getRoot().baseline().autoAdjust().enabled(true);
+        configurator.getRoot().baseline().nodes().get("node1").autoAdjustEnabled(true);
+
+        try{
+            configurator.getRoot().baseline().autoAdjust().enabled(false);
+            Assertions.fail();
+        } catch (ConfigurationValidationException e) {}
+
+        PublicConfigurator<LocalConfiguration> con = new PublicConfigurator<>(configurator);
+    }
+
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/AutoAdjustValidator.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/AutoAdjustValidator.java
new file mode 100644
index 0000000..fdd2d58
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/AutoAdjustValidator.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.sample.validation;
+
+import org.apache.ignite.configuration.sample.LocalConfiguration;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.FieldValidator;
+
+/**
+ * Test validator for AutoAdjust.
+ */
+public class AutoAdjustValidator extends FieldValidator<Number, LocalConfiguration> {
+    /** Constructor. */
+    public AutoAdjustValidator(String message) {
+        super(message);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void validate(Number value, LocalConfiguration newRoot, LocalConfiguration oldRoot) throws ConfigurationValidationException {
+        final Boolean isEnabled = newRoot.baseline().autoAdjust().enabled().value();
+
+        if (value.longValue() > 0 && !isEnabled)
+            throw new ConfigurationValidationException(message);
+    }
+
+}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/AutoAdjustValidator2.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/AutoAdjustValidator2.java
new file mode 100644
index 0000000..5ab1bba
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/AutoAdjustValidator2.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.sample.validation;
+
+import org.apache.ignite.configuration.sample.LocalConfiguration;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.FieldValidator;
+
+/**
+ * Test validator for AutoAdjust (works the same as the other one).
+ */
+public class AutoAdjustValidator2 extends FieldValidator<Number, LocalConfiguration> {
+    /** Constructor. */
+    public AutoAdjustValidator2(String message) {
+        super(message);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void validate(Number value, LocalConfiguration newRoot, LocalConfiguration oldRoot) throws ConfigurationValidationException {
+        final Boolean isEnabled = newRoot.baseline().autoAdjust().enabled().value();
+
+        if (value.longValue() > 0 && !isEnabled)
+            throw new ConfigurationValidationException(message);
+    }
+
+}
\ No newline at end of file
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/NodeValidator.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/NodeValidator.java
new file mode 100644
index 0000000..5c99658
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/configuration/sample/validation/NodeValidator.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.sample.validation;
+
+import org.apache.ignite.configuration.sample.LocalConfiguration;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.FieldValidator;
+
+/**
+ * Test Node configuration validator.
+ */
+public class NodeValidator extends FieldValidator<Boolean, LocalConfiguration> {
+    /** Constructor. */
+    public NodeValidator(String message) {
+        super(message);
+    }
+
+    @Override public void validate(Boolean value, LocalConfiguration newRoot, LocalConfiguration oldRoot) throws ConfigurationValidationException {
+        if (value != null && value) {
+            if (!newRoot.baseline().autoAdjust().enabled().value()) {
+                throw new ConfigurationValidationException("");
+            }
+        }
+    }
+}
diff --git a/modules/configuration-annotation-processor/src/test/resources/org/apache/ignite/configuration/processor/internal/TestConfigurationSchema.java b/modules/configuration-annotation-processor/src/test/resources/org/apache/ignite/configuration/processor/internal/TestConfigurationSchema.java
new file mode 100644
index 0000000..5875b7d
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/resources/org/apache/ignite/configuration/processor/internal/TestConfigurationSchema.java
@@ -0,0 +1,22 @@
+package org.apache.ignite.configuration.processor.internal;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.Value;
+
+@Config(value = "test", root = true)
+public class TestConfigurationSchema {
+    @Value
+    private String value1;
+
+    @Value
+    private long primitiveLong;
+
+    @Value
+    private Long boxedLong;
+
+    @Value
+    private int primitiveInt;
+
+    @Value
+    private Integer boxedInt;
+}
\ No newline at end of file
diff --git a/modules/configuration/pom.xml b/modules/configuration/pom.xml
new file mode 100644
index 0000000..7eaa507
--- /dev/null
+++ b/modules/configuration/pom.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  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.
+-->
+
+<!--
+    POM file.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.ignite</groupId>
+    <artifactId>ignite-configuration</artifactId>
+    <version>3.0-SNAPSHOT</version>
+    <url>http://ignite.apache.org</url>
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>javax.validation</groupId>
+            <artifactId>validation-api</artifactId>
+            <version>2.0.1.Final</version>
+        </dependency>
+
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <version>1.2.17</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>3.4.6</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationProperty.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationProperty.java
new file mode 100644
index 0000000..4947b7e
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationProperty.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.configuration;
+
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+
+/**
+ * Base interface for configuration.
+ * @param <VALUE> Type of the value.
+ * @param <CHANGE> Type of the object that changes the value of configuration.
+ */
+public interface ConfigurationProperty<VALUE, CHANGE> {
+    /**
+     * Get key of this node.
+     * @return Key.
+     */
+    String key();
+
+    /**
+     * Get value of this property.
+     * @return Value of this property.
+     */
+    VALUE value();
+
+    /**
+     * Change this configuration node value.
+     * @param change CHANGE object.
+     * @throws ConfigurationValidationException If validation failed.
+     */
+    void change(CHANGE change) throws ConfigurationValidationException;
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationTree.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationTree.java
new file mode 100644
index 0000000..2df36b7
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationTree.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration;
+
+import java.util.Map;
+
+/**
+ * Configuration tree with configuration values and other configuration trees as child nodes.
+ * @param <VALUE> Value type of the node.
+ * @param <CHANGE> Type of the object that changes this node's value.
+ */
+public interface ConfigurationTree<VALUE, CHANGE> extends ConfigurationProperty<VALUE, CHANGE> {
+    /** Children of the tree. */
+    Map<String, ConfigurationProperty<?, ?>> members();
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java
new file mode 100644
index 0000000..5e6229c
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/ConfigurationValue.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+/**
+ * Configuration value.
+ * @param <VALUE> Type of the value.
+ */
+public interface ConfigurationValue<VALUE> extends ConfigurationProperty<VALUE, VALUE> {
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/Configurator.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/Configurator.java
new file mode 100644
index 0000000..ba04aca
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/Configurator.java
@@ -0,0 +1,207 @@
+/*
+ * 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 java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import org.apache.ignite.configuration.internal.DynamicConfiguration;
+import org.apache.ignite.configuration.internal.DynamicProperty;
+import org.apache.ignite.configuration.internal.Modifier;
+import org.apache.ignite.configuration.internal.selector.Selector;
+import org.apache.ignite.configuration.storage.ConfigurationStorage;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.FieldValidator;
+import org.apache.ignite.configuration.internal.validation.MemberKey;
+
+/**
+ * Convenient wrapper for configuration root. Provides access to configuration tree, stores validators, performs actions
+ * on configuration such as initialized, change and view.
+ * @param <T> Type of configuration root.
+ */
+public class Configurator<T extends DynamicConfiguration<?, ?, ?>> {
+    /** Storage for the configuration tree. */
+    private final ConfigurationStorage storage;
+
+    /** Root of the configuration tree. */
+    private final T root;
+
+    /** Configuration property validators. */
+    private final Map<MemberKey, List<FieldValidator<? extends Serializable, T>>> fieldValidators = new HashMap<>();
+
+    /**
+     *
+     * @param storage
+     * @param rootBuilder
+     * @param <VIEW>
+     * @param <INIT>
+     * @param <CHANGE>
+     * @param <CONF>
+     * @return
+     */
+    public static <VIEW, INIT, CHANGE, CONF extends DynamicConfiguration<VIEW, INIT, CHANGE>> Configurator<CONF> create(
+        ConfigurationStorage storage,
+        Function<Configurator<CONF>, CONF> rootBuilder
+    ) {
+        return new Configurator<>(storage, rootBuilder, null);
+    }
+
+    /**
+     *
+     * @param storage
+     * @param rootBuilder
+     * @param init
+     * @param <VIEW>
+     * @param <INIT>
+     * @param <CHANGE>
+     * @param <CONF>
+     * @return
+     */
+    public static <VIEW, INIT, CHANGE, CONF extends DynamicConfiguration<VIEW, INIT, CHANGE>> Configurator<CONF> create(
+        ConfigurationStorage storage,
+        Function<Configurator<CONF>, CONF> rootBuilder,
+        INIT init
+    ) {
+        return new Configurator<>(storage, rootBuilder, init);
+    }
+
+    /**
+     * Constructor.
+     * @param storage Configuration storage.
+     * @param rootBuilder Function, that creates configuration root.
+     */
+    private <VIEW, INIT, CHANGE, CONF extends DynamicConfiguration<VIEW, INIT, CHANGE>> Configurator(
+        ConfigurationStorage storage,
+        Function<Configurator<CONF>, CONF> rootBuilder,
+        INIT init
+    ) {
+        this.storage = storage;
+
+        final CONF built = rootBuilder.apply((Configurator<CONF>) this);
+
+        if (init != null)
+            built.init(init);
+
+        root = (T) built;
+    }
+
+    /**
+     *
+     * @param selector
+     * @param <TARGET>
+     * @param <VIEW>
+     * @param <INIT>
+     * @param <CHANGE>
+     * @return
+     */
+    public <TARGET extends Modifier<VIEW, INIT, CHANGE>, VIEW, INIT, CHANGE> VIEW getPublic(
+        Selector<T, TARGET, VIEW, INIT, CHANGE> selector
+    ) {
+        return selector.select(root).value();
+    }
+
+    /**
+     *
+     * @param selector
+     * @param newValue
+     * @param <TARGET>
+     * @param <VIEW>
+     * @param <INIT>
+     * @param <CHANGE>
+     * @throws ConfigurationValidationException
+     */
+    public <TARGET extends Modifier<VIEW, INIT, CHANGE>, VIEW, INIT, CHANGE> void set(
+        Selector<T, TARGET, VIEW, INIT, CHANGE> selector,
+        CHANGE newValue
+    ) throws ConfigurationValidationException {
+        // TODO: atomic change start
+        final T copy = (T) root.copy();
+
+        final TARGET select = selector.select(copy);
+        select.changeWithoutValidation(newValue);
+        copy.validate(root);
+        selector.select(root).changeWithoutValidation(newValue);
+        // TODO: atomic change end
+    }
+
+    /**
+     *
+     * @param selector
+     * @param <TARGET>
+     * @param <VIEW>
+     * @param <INIT>
+     * @param <CHANGE>
+     * @return
+     */
+    public <TARGET extends Modifier<VIEW, INIT, CHANGE>, VIEW, INIT, CHANGE> ConfigurationProperty<VIEW, CHANGE> getInternal(
+        Selector<T, TARGET, VIEW, INIT, CHANGE> selector
+    ) {
+        return selector.select(root);
+    }
+
+    /**
+     *
+     * @param aClass
+     * @param key
+     * @param validators
+     * @param <PROP>
+     */
+    public <PROP extends Serializable> void addValidations(
+        Class<? extends ConfigurationTree<?, ?>> aClass,
+        String key,
+        List<FieldValidator<? super PROP, ? extends ConfigurationTree<?, ?>>> validators
+    ) {
+        fieldValidators.put(new MemberKey(aClass, key), (List) validators);
+    }
+
+    /**
+     * Get all validators for given member key (class + field).
+     * @param key Member key.
+     * @return Validators.
+     */
+    public List<FieldValidator<? extends Serializable, T>> validators(MemberKey key) {
+        return fieldValidators.getOrDefault(key, Collections.emptyList());
+    }
+
+    /**
+     * Get configuration root.
+     * @return Configuration root.
+     */
+    public T getRoot() {
+        return root;
+    }
+
+    /**
+     * Execute on property attached to configurator.
+     * @param property Property.
+     * @param <PROP> Type of the property.
+     */
+    public <PROP extends Serializable> void onAttached(DynamicProperty<PROP> property) {
+        final String key = property.key();
+        property.addListener(new PropertyListener<PROP, PROP>() {
+            /** {@inheritDoc} */
+            public void update(PROP newValue, ConfigurationProperty<PROP, PROP> modifier) {
+                storage.save(key, newValue);
+            }
+        });
+        storage.listen(key, property::setSilently);
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/PropertyListener.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/PropertyListener.java
new file mode 100644
index 0000000..52f3522
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/PropertyListener.java
@@ -0,0 +1,61 @@
+/*
+ * 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 java.io.Serializable;
+
+/**
+ * Configuration property change listener.
+ *
+ * @param <VIEW> VIEW type of property.
+ * @param <CHANGE> CHANGE type of property.
+ */
+public interface PropertyListener<VIEW extends Serializable, CHANGE extends Serializable> {
+    /**
+     * Called before property value is updated.
+     *
+     * @param oldValue Previous value.
+     * @param newValue New value.
+     * @param modifier Property itself.
+     * @return {@code true} if changes are approved and {@code false} then property update must be aborted.
+     */
+    default boolean beforeUpdate(VIEW oldValue, VIEW newValue, ConfigurationProperty<VIEW, CHANGE> modifier) {
+        return true;
+    }
+
+    /**
+     * Called on property value update.
+     *
+     * @param newValue New value of the property.
+     * @param modifier Property itself.
+     */
+    default void update(VIEW newValue, ConfigurationProperty<VIEW, CHANGE> modifier) {
+        /* No-op */
+    }
+
+    /**
+     * Called after property value update.
+     *
+     * @param newValue New value of the property.
+     * @param modifier Property itself.
+     */
+    default void afterUpdate(VIEW newValue, ConfigurationProperty<VIEW, CHANGE> modifier) {
+        /* No-op */
+    }
+
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/PublicConfigurator.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/PublicConfigurator.java
new file mode 100644
index 0000000..7b8f953
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/PublicConfigurator.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration;
+
+import org.apache.ignite.configuration.internal.DynamicConfiguration;
+
+/**
+ * Public configurator.
+ * @param <T> Public type.
+ */
+public class PublicConfigurator<T extends ConfigurationTree<?, ?>> {
+    /** Configuration root. */
+    private T root;
+
+    public <VIEW, INIT, CHANGE> PublicConfigurator(Configurator<? extends DynamicConfiguration<VIEW, INIT, CHANGE>> configurator) {
+        final ConfigurationTree<VIEW, CHANGE> root = configurator.getRoot();
+        this.root = (T) root;
+    }
+
+    /**
+     * Get root of the configuration.
+     * @return Configuration root.
+     */
+    public T getRoot() {
+        return root;
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Config.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Config.java
new file mode 100644
index 0000000..6328d34
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Config.java
@@ -0,0 +1,69 @@
+/*
+ * 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 org.apache.ignite.configuration.internal.DynamicConfiguration;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * This annotation, if applied to a class, marks it as a configuration schema.
+ * Annotation processor generates several classes for each configuration schema:
+ * <ul>
+ * <li>Config - Represents configuration itself, provides API to init, change and view it. Extends {@link DynamicConfiguration}</li>
+ * <li>Init - initializes config tree</li>
+ * <li>Change - changes config tree</li>
+ * <li>View - immutable object to view config tree</li>
+ * </ul>
+ *
+ * <h1 class="header">Example</h1>
+ * Here is how to create a root configuration schema:
+ * <pre name="code" class="java">
+ * {@literal @}Config(value = "local", root = true)
+ * public class LocalConfigurationSchema {
+ *
+ *      {@literal @}Value
+ *      private String foo;
+ *
+ *      {@literal @}Value
+ *      private boolean bar;
+ *
+ *      {@literal @}ConfigValue
+ *      private SomeOtherConfiguration someOther;
+ *
+ * }
+ * </pre>
+ */
+@Target({ TYPE })
+@Retention(SOURCE)
+@Documented
+public @interface Config {
+    /**
+     * @return The name of the configuration.
+     */
+    String value() default "";
+
+    /**
+     * @return {@code true } if marked class is the root of the configuration schema.
+     */
+    boolean root() default false;
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java
new file mode 100644
index 0000000..b593674
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/ConfigValue.java
@@ -0,0 +1,47 @@
+/*
+ * 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.SOURCE;
+
+/**
+ * This annotation marks configuration schema field as a configuration tree node.
+ * <pre name="code" class="java">
+ * {@literal @}Config
+ * public class FooConfigurationSchema {
+ *
+ *      {@literal @}ConfigValue
+ *      private SomeOtherConfiguration someOther;
+ *
+ * }
+ * </pre>
+ */
+@Target({ FIELD })
+@Retention(SOURCE)
+@Documented
+public @interface ConfigValue {
+    /**
+     * @return The name of the configuration.
+     */
+    String value() default "";
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/NamedConfigValue.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/NamedConfigValue.java
new file mode 100644
index 0000000..f3d3f77
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/NamedConfigValue.java
@@ -0,0 +1,51 @@
+/*
+ * 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 org.apache.ignite.configuration.internal.NamedListConfiguration;
+import org.apache.ignite.configuration.internal.NamedList;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * This annotation denotes configuration schema fields that are dynamically created and mapped by name.
+ * Example use-cases for this annotation are Ignite node configuration, cache configuration, because nodes and caches
+ * can be added dynamically.
+ * Every field annotated with this annotation will produce a {@link NamedListConfiguration} field in generated configuration class.
+ *
+ * <h1 class="header">Example</h1>
+ * <pre name="code" class="java">
+ * {@literal @}Config(value = "local", root = true)
+ * public class LocalConfigurationSchema {
+ *
+ *      {@literal @}NamedConfigValue
+ *      private SomeOtherConfiguration someOther;
+ *
+ * }
+ * </pre>
+ * @see NamedList
+ */
+@Target({ FIELD })
+@Retention(SOURCE)
+@Documented
+public @interface NamedConfigValue {
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Validate.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Validate.java
new file mode 100644
index 0000000..2c2c34d
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Validate.java
@@ -0,0 +1,75 @@
+/*
+ * 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.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import org.apache.ignite.configuration.validation.FieldValidator;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * This annotation applies custom validation to configuration field, for example:
+ * <pre name="code" class="java">
+ * public class ConfSchema {
+ *     {@literal @}Validate(SomeCustomValidator.class)
+ *     private String value;
+ * }
+ * </pre>
+ *
+ * If you need multiple custom validations:
+ * <pre name="code" class="java">
+ * public class ConfSchema {
+ *     {@literal @}Validate(SomeCustomValidator.class),
+ *     {@literal @}Validate(SomeOtherCustomValidator.class)
+ *     private String value;
+ * }
+ * </pre>
+ */
+@Target({ FIELD })
+@Retention(SOURCE)
+@Repeatable(Validate.List.class)
+@Documented
+public @interface Validate {
+    /**
+     * @return Validation class that is going to be instantiated for the field validation.
+     */
+    Class<? extends FieldValidator<?, ?>> value();
+
+    /**
+     * @return Validation error message.
+     */
+    String message() default "";
+
+    /**
+     * Defines several {@link Validate} annotations on the same element.
+     *
+     * @see Validate
+     */
+    @Target({ FIELD })
+    @Retention(SOURCE)
+    @Documented
+    @interface List {
+
+        Validate[] value();
+    }
+
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Value.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Value.java
new file mode 100644
index 0000000..eeda647
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/annotation/Value.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 org.apache.ignite.configuration.internal.DynamicProperty;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * This annotation marks configuration schema field as a configuration tree leaf.
+ * Every field annotated with this annotation will produce a {@link DynamicProperty} field in generated configuration class.
+ */
+@Target({ FIELD })
+@Retention(SOURCE)
+@Documented
+public @interface Value {
+    /**
+     * @return {@code true} if this value can only be initialized and can't be changed afterwards.
+     */
+    boolean immutable() default false;
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicConfiguration.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicConfiguration.java
new file mode 100644
index 0000000..a739f7c
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicConfiguration.java
@@ -0,0 +1,150 @@
+/*
+ * 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.internal;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.ignite.configuration.ConfigurationProperty;
+import org.apache.ignite.configuration.ConfigurationTree;
+import org.apache.ignite.configuration.Configurator;
+import org.apache.ignite.configuration.internal.selector.BaseSelectors;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.FieldValidator;
+
+/**
+ * This class represents configuration root or node.
+ */
+public abstract class DynamicConfiguration<VIEW, INIT, CHANGE> implements Modifier<VIEW, INIT, CHANGE>, ConfigurationTree<VIEW, CHANGE> {
+    /** Fully qualified name of the configuration. */
+    protected final String qualifiedName;
+
+    /** Configuration key. */
+    protected final String key;
+
+    /** Configuration prefix. */
+    protected final String prefix;
+
+    /** Configuration members (leaves and nodes). */
+    protected final Map<String, Modifier<?, ?, ?>> members = new HashMap<>();
+
+    /** Root configuration node. */
+    protected final DynamicConfiguration<?, ?, ?> root;
+
+    /** {@code true} if this is a member of {@link NamedListConfiguration}. */
+    protected final boolean isNamed;
+
+    /** Configurator that this configuration is attached to. */
+    protected final Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator;
+
+    /**
+     * Constructor.
+     * @param prefix Configuration prefix.
+     * @param key Configuration key.
+     * @param isNamed Is this a part of named configuration.
+     * @param configurator Configurator that this object is attached to.
+     * @param root Root configuration.
+     */
+    protected DynamicConfiguration(
+        String prefix,
+        String key,
+        boolean isNamed,
+        Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator,
+        DynamicConfiguration<?, ?, ?> root
+    ) {
+        this.prefix = prefix;
+        this.isNamed = isNamed;
+        this.configurator = configurator;
+
+        this.key = key;
+        if (root == null)
+            this.qualifiedName = key;
+        else {
+            if (isNamed)
+                qualifiedName = String.format("%s[%s]", prefix, key);
+            else
+                qualifiedName = String.format("%s.%s", prefix, key);
+        }
+
+        this.root = root != null ? root : this;
+    }
+
+    /**
+     * Add new configuration member.
+     * @param member Configuration member (leaf or node).
+     * @param <M> Type of member.
+     */
+    protected <M extends Modifier<?, ?, ?>> void add(M member) {
+        members.put(member.key(), member);
+    }
+
+    /**
+     * Add new configuration member with validators.
+     * @param member Configuration member (leaf or node).
+     * @param validators Validators for new member.
+     * @param <PROP> Type of {@link DynamicProperty}.
+     * @param <M> Type of member.
+     */
+    protected <PROP extends Serializable, M extends DynamicProperty<PROP>> void add(
+        M member,
+        List<FieldValidator<? super PROP, ? extends ConfigurationTree<?, ?>>> validators
+    ) {
+        members.put(member.key(), member);
+
+        configurator.addValidations((Class<? extends ConfigurationTree<?, ?>>) getClass(), member.key(), validators);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void change(CHANGE change) throws ConfigurationValidationException {
+        configurator.set(BaseSelectors.find(qualifiedName), change);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String key() {
+        return key;
+    }
+
+    /**
+     * Create a deep copy of this DynamicConfiguration, but attaching it to another configuration root.
+     * @param root New configuration root.
+     * @return Copy of this configuration.
+     */
+    public abstract DynamicConfiguration<VIEW, INIT, CHANGE> copy(DynamicConfiguration<?, ?, ?> root);
+
+    /**
+     * Create a deep copy of this DynamicConfiguration, making it root configuration (so this method must be called
+     * only on root configuration object).
+     * @return Copy of this configuration.
+     */
+    public final DynamicConfiguration<VIEW, INIT, CHANGE> copy() {
+        return copy(null);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void validate(DynamicConfiguration<?, ?, ?> oldRoot) throws ConfigurationValidationException {
+        for (Modifier<?, ?, ?> member : members.values())
+            member.validate(oldRoot);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Map<String, ConfigurationProperty<?, ?>> members() {
+        return members.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicProperty.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicProperty.java
new file mode 100644
index 0000000..3ab17db
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/DynamicProperty.java
@@ -0,0 +1,203 @@
+/*
+ * 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.internal;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.ignite.configuration.ConfigurationValue;
+import org.apache.ignite.configuration.Configurator;
+import org.apache.ignite.configuration.PropertyListener;
+import org.apache.ignite.configuration.internal.selector.BaseSelectors;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.FieldValidator;
+import org.apache.ignite.configuration.internal.validation.MemberKey;
+
+/**
+ * Holder for property value. Expected to be used with numbers, strings and other immutable objects, e.g. IP addresses.
+ */
+public class DynamicProperty<T extends Serializable> implements Modifier<T, T, T>, ConfigurationValue<T> {
+    /** Name of property. */
+    private final String name;
+
+    /** Member key. */
+    private final MemberKey memberKey;
+
+    /** Full name with prefix. */
+    private final String qualifiedName;
+
+    /** Property value. */
+    protected volatile T val;
+
+    /** Listeners of property update. */
+    private final List<PropertyListener<T, T>> updateListeners = new ArrayList<>();
+
+    /** Configurator that this configuration is attached to. */
+    protected final Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator;
+
+    /** Configuration root. */
+    protected final DynamicConfiguration<?, ?, ?> root;
+
+    /**
+     * Constructor.
+     * @param prefix Property prefix.
+     * @param name Property name.
+     * @param memberKey Property member key.
+     * @param configurator Configurator to attach to.
+     * @param root Configuration root.
+     */
+    public DynamicProperty(
+        String prefix,
+        String name,
+        MemberKey memberKey,
+        Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator,
+        DynamicConfiguration<?, ?, ?> root
+    ) {
+        this(prefix, name, memberKey, null, configurator, root);
+    }
+
+    /**
+     * Constructor.
+     * @param prefix Property prefix.
+     * @param name Property name.
+     * @param memberKey Property member key.
+     * @param defaultValue Default value for the property.
+     * @param configurator Configurator to attach to.
+     * @param root Configuration root.
+     */
+    public DynamicProperty(
+        String prefix,
+        String name,
+        MemberKey memberKey,
+        T defaultValue,
+        Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator,
+        DynamicConfiguration<?, ?, ?> root
+    ) {
+        this(defaultValue, name, memberKey, String.format("%s.%s", prefix, name), configurator, root, false);
+    }
+
+    /**
+     * Copy constructor.
+     * @param base Property to copy from.
+     * @param root Configuration root.
+     */
+    private DynamicProperty(
+        DynamicProperty<T> base,
+        DynamicConfiguration<?, ?, ?> root
+    ) {
+        this(base.val, base.name, base.memberKey, base.qualifiedName, base.configurator, root, true);
+    }
+
+    /**
+     * Constructor.
+     * @param value Property value.
+     * @param name Property name.
+     * @param memberKey Member key.
+     * @param qualifiedName Fully qualified name of the property.
+     * @param configurator Configurator.
+     * @param root Configuration root.
+     */
+    private DynamicProperty(
+        T value,
+        String name,
+        MemberKey memberKey,
+        String qualifiedName,
+        Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator,
+        DynamicConfiguration<?, ?, ?> root,
+        boolean isCopy
+    ) {
+        this.name = name;
+        this.memberKey = memberKey;
+        this.qualifiedName = qualifiedName;
+        this.val = value;
+        this.configurator = configurator;
+        this.root = root;
+
+        if (isCopy)
+            configurator.onAttached(this);
+    }
+
+    /**
+     * Add change listener to this property.
+     * @param listener Property change listener.
+     */
+    public void addListener(PropertyListener<T, T> listener) {
+        updateListeners.add(listener);
+    }
+
+    /** {@inheritDoc} */
+    public T value() {
+        return val;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void change(T object) throws ConfigurationValidationException {
+        configurator.set(BaseSelectors.find(qualifiedName), object);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void init(T object) throws ConfigurationValidationException {
+        this.val = object;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void changeWithoutValidation(T object) {
+        this.val = object;
+
+        for (PropertyListener<T, T> listener : updateListeners)
+            listener.update(object, this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void validate(DynamicConfiguration<?, ?, ?> oldRoot) throws ConfigurationValidationException {
+        final List<? extends FieldValidator<? extends Serializable, ? extends DynamicConfiguration<?, ?, ?>>> validators = configurator.validators(memberKey);
+
+        for (FieldValidator<? extends Serializable, ? extends DynamicConfiguration<?, ?, ?>> validator : validators)
+            ((FieldValidator<T, DynamicConfiguration<?, ?, ?>>) validator).validate(val, root, oldRoot);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String key() {
+        return name;
+    }
+
+    /**
+     * Get fully qualified name of this property.
+     * @return Fully qualified name.
+     */
+    public String qualifiedName() {
+        return qualifiedName;
+    }
+
+    public void setSilently(T serializable) {
+        val = serializable;
+
+        for (PropertyListener<T, T> listener : updateListeners)
+            listener.update(val, this);
+    }
+
+    /**
+     * Create a deep copy of this DynamicProperty, but attaching it to another configuration root.
+     * @param newRoot New configuration root.
+     * @return Copy of this property.
+     */
+    public DynamicProperty<T> copy(DynamicConfiguration<?, ?, ?> newRoot) {
+        return new DynamicProperty<>(this, newRoot);
+    }
+
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/Modifier.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/Modifier.java
new file mode 100644
index 0000000..eb123f4
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/Modifier.java
@@ -0,0 +1,48 @@
+/*
+ * 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.internal;
+
+import org.apache.ignite.configuration.ConfigurationProperty;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+
+/**
+ * Interface for configuration nodes and leaves.
+ */
+public interface Modifier<VIEW, INIT, CHANGE> extends ConfigurationProperty<VIEW, CHANGE> {
+    /**
+     * Change this configuration node value, but without validation.
+     * FIXME: this is a necessary evil, but this should'n be accessed from outside of the configurator.
+     * @param change CHANGE object.
+     */
+    void changeWithoutValidation(CHANGE change);
+
+    /**
+     * Initialize this configuration node with value.
+     * @param init INIT object.
+     * @throws ConfigurationValidationException If validation failed.
+     */
+    void init(INIT init) throws ConfigurationValidationException;
+
+    /**
+     * Validate this configuration node against old configuration root thus comparing new configuration "snapshot"
+     * with a previous one.
+     * @param oldRoot Old configuration root.
+     * @throws ConfigurationValidationException If validation failed.
+     */
+    void validate(DynamicConfiguration<?, ?, ?> oldRoot) throws ConfigurationValidationException;
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedList.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedList.java
new file mode 100644
index 0000000..edce953
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedList.java
@@ -0,0 +1,44 @@
+/*
+ * 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.internal;
+
+import java.util.Map;
+
+/**
+ * This class holds named configurations in VIEW object.
+ */
+public class NamedList<T> {
+    /** Named values. */
+    private final Map<String, T> values;
+
+    /**
+     * Constructor.
+     * @param values Named values.
+     */
+    public NamedList(Map<String, T> values) {
+        this.values = values;
+    }
+
+    /**
+     * Get named values.
+     * @return Named values.
+     */
+    public Map<String, T> getValues() {
+        return values;
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedListConfiguration.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedListConfiguration.java
new file mode 100644
index 0000000..98e2619
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/NamedListConfiguration.java
@@ -0,0 +1,126 @@
+/*
+ * 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.internal;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+import org.apache.ignite.configuration.Configurator;
+
+/**
+ * Named configuration wrapper.
+ */
+public class NamedListConfiguration<VIEW, T extends Modifier<VIEW, INIT, CHANGE>, INIT, CHANGE>
+    extends DynamicConfiguration<NamedList<VIEW>, NamedList<INIT>, NamedList<CHANGE>> {
+    /** Creator of named configuration. */
+    private final BiFunction<String, String, T> creator;
+
+    /** Named configurations. */
+    private final Map<String, T> values = new HashMap<>();
+
+    /**
+     * Constructor.
+     * @param prefix Configuration prefix.
+     * @param key Configuration key.
+     * @param configurator Configurator that this object is attached to.
+     * @param root Root configuration.
+     * @param creator Underlying configuration creator function.
+     */
+    public NamedListConfiguration(
+        String prefix,
+        String key,
+        Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator,
+        DynamicConfiguration<?, ?, ?> root,
+        BiFunction<String, String, T> creator
+    ) {
+        super(prefix, key, false, configurator, root);
+        this.creator = creator;
+    }
+
+    /**
+     * Copy constructor.
+     * @param base Base to copy from.
+     * @param configurator Configurator to attach to.
+     * @param root Root of the configuration.
+     */
+    private NamedListConfiguration(
+        NamedListConfiguration<VIEW, T, INIT, CHANGE> base,
+        Configurator<? extends DynamicConfiguration<?, ?, ?>> configurator,
+        DynamicConfiguration<?, ?, ?> root
+    ) {
+        super(base.prefix, base.key, false, configurator, root);
+
+        this.creator = base.creator;
+
+        for (Map.Entry<String, T> entry : base.values.entrySet()) {
+            String k = entry.getKey();
+            T value = entry.getValue();
+
+            final T copy = (T) ((DynamicConfiguration<VIEW, INIT, CHANGE>) value).copy(root);
+            add(copy);
+
+            this.values.put(k, copy);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void init(NamedList<INIT> list) {
+        list.getValues().forEach((key, init) -> {
+            if (!values.containsKey(key)) {
+                final T created = creator.apply(qualifiedName, key);
+                add(created);
+                values.put(key, created);
+            }
+
+            values.get(key).init(init);
+        });
+    }
+
+    /**
+     * Get named configuration by name.
+     * @param name Name.
+     * @return Configuration.
+     */
+    public T get(String name) {
+        return values.get(name);
+    }
+
+    /** {@inheritDoc} */
+    @Override public NamedList<VIEW> value() {
+        return new NamedList<>(values.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, it -> it.getValue().value())));
+    }
+
+    /** {@inheritDoc} */
+    @Override public void changeWithoutValidation(NamedList<CHANGE> list) {
+        list.getValues().forEach((key, change) -> {
+            if (!values.containsKey(key)) {
+                final T created = creator.apply(qualifiedName, key);
+                add(created);
+                values.put(key, created);
+            }
+
+            values.get(key).changeWithoutValidation(change);
+        });
+    }
+
+    /** {@inheritDoc} */
+    @Override public NamedListConfiguration<VIEW, T, INIT, CHANGE> copy(DynamicConfiguration<?, ?, ?> root) {
+        return new NamedListConfiguration<>(this, configurator, root);
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/BaseSelectors.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/BaseSelectors.java
new file mode 100644
index 0000000..f219b73
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/BaseSelectors.java
@@ -0,0 +1,157 @@
+/*
+ * 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.internal.selector;
+
+import java.lang.invoke.MethodHandle;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.ignite.configuration.internal.DynamicConfiguration;
+import org.apache.ignite.configuration.internal.Modifier;
+
+/**
+ * Base selector holder.
+ */
+public class BaseSelectors {
+    /** Map from string representation of selector to {@link SelectorHolder}. */
+    private static final Map<String, SelectorHolder> selectors = new HashMap<>();
+
+    /**
+     * Get selector from selectors map by key.
+     *
+     * Valid formats for selector key:
+     * <ul>
+     *     <li>root.inner.option.field in case of static config field</li>
+     *     <li>root.inner.named[name].field in case of dynamic (named) config field</li>
+     * </ul>
+     *
+     * @param name Selector name.
+     * @return Selector.
+     */
+    public static <ROOT extends DynamicConfiguration<?, ?, ?>, TARGET extends Modifier<VIEW, INIT, CHANGE>, VIEW, INIT, CHANGE> Selector<ROOT, TARGET, VIEW, INIT, CHANGE> find(String name) {
+        String[] nameParts = name.split("\\.");
+
+        List<String> arguments = new ArrayList<>();
+        StringBuilder keyBuilder = new StringBuilder();
+
+        for (int i = 0; i < nameParts.length; i++) {
+            String part = nameParts[i];
+
+            int start = part.indexOf('[');
+
+            String methodArg = null;
+
+            if (start != -1) {
+                int end = part.indexOf(']');
+
+                if (end != -1) {
+                    methodArg = part.substring(start + 1, end);
+                    part = part.substring(0, start);
+                }
+            }
+
+            if (methodArg != null)
+                arguments.add(methodArg);
+
+            keyBuilder.append(part);
+
+            if (i != nameParts.length - 1)
+                keyBuilder.append('.');
+        }
+
+        final String key = keyBuilder.toString();
+
+        final SelectorHolder selector = selectors.get(key);
+
+        if (selector == null) {
+            final int lastDot = key.lastIndexOf('.');
+
+            if (lastDot != -1) {
+                String partialKey = key.substring(0, lastDot);
+
+                final SelectorHolder partialSelector = selectors.get(partialKey);
+
+                if (partialSelector != null) {
+                    final String availableOptions = selectors.keySet().stream().filter(s -> s.startsWith(partialKey)).collect(Collectors.joining(", "));
+                    throw new SelectorNotFoundException("Selector " + key + " was not found, available options are: " + availableOptions);
+                }
+            }
+        }
+
+        try {
+            return (Selector<ROOT, TARGET, VIEW, INIT, CHANGE>) selector.get(arguments);
+        } catch (Throwable throwable) {
+            throw new SelectorNotFoundException("Failed to get selector: " + throwable.getMessage(), throwable);
+        }
+    }
+
+    /**
+     * Put selector to selectors map by key.
+     * @param key Selector key.
+     * @param selector Selector.
+     */
+    public static void put(String key, Selector<?, ?, ?, ?, ?> selector) {
+        selectors.put(key, new SelectorHolder(selector));
+    }
+
+    /**
+     * Put method handle selector (for named configuration) to selectors map by key.
+     * @param key Selector key.
+     * @param handle Method handle.
+     */
+    public static void put(String key, MethodHandle handle) {
+        selectors.put(key, new SelectorHolder(handle));
+    }
+
+    /**
+     * Holder for selector (it's either selector object or method handle for named configurations).
+     */
+    private static final class SelectorHolder {
+        /** Selector object. */
+        Selector<?, ?, ?, ?, ?> selector;
+
+        /** Method handle for named configuration. */
+        MethodHandle selectorFn;
+
+        /** Constructor for selector. */
+        public SelectorHolder(Selector<?, ?, ?, ?, ?> selector) {
+            this.selector = selector;
+        }
+
+        /** Constructor for method handle. */
+        public SelectorHolder(MethodHandle selectorFn) {
+            this.selectorFn = selectorFn;
+        }
+
+        /**
+         * Get selector object.
+         * @param arguments Arguments (empty if static selector or contains names for named configuration).
+         * @return Selector.
+         * @throws Throwable If failed to invoke method handle.
+         */
+        Selector<?, ?, ?, ?, ?> get(List<String> arguments) throws Throwable {
+            if (selector != null)
+                return selector;
+
+            return (Selector<?, ?, ?, ?, ?>) selectorFn.invokeWithArguments(arguments);
+        }
+
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/Selector.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/Selector.java
new file mode 100644
index 0000000..177e2c2
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/Selector.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.internal.selector;
+
+import org.apache.ignite.configuration.internal.Modifier;
+
+/**
+ * Interface for objects helping select configuration elements.
+ *
+ * @param <ROOT> Root configuration class.
+ * @param <TARGET> Target configuration class.
+ * @param <VIEW> View class of target.
+ * @param <INIT> Init class of target.
+ * @param <CHANGE> Change class of target.
+ */
+public interface Selector<ROOT, TARGET extends Modifier<VIEW, INIT, CHANGE>, VIEW, INIT, CHANGE> {
+    /**
+     * Select configuration element.
+     *
+     * @param root Configuration root object.
+     * @return Configuration element.
+     */
+    TARGET select(ROOT root);
+
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/SelectorNotFoundException.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/SelectorNotFoundException.java
new file mode 100644
index 0000000..39d9381
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/selector/SelectorNotFoundException.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.configuration.internal.selector;
+
+/**
+ * Exception for absence of selector.
+ */
+public class SelectorNotFoundException extends RuntimeException {
+    /** Constructor. */
+    public SelectorNotFoundException(String message) {
+        super(message);
+    }
+
+    /** Constructor. */
+    public SelectorNotFoundException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MaxValidator.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MaxValidator.java
new file mode 100644
index 0000000..60620fe
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MaxValidator.java
@@ -0,0 +1,44 @@
+/*
+ * 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.internal.validation;
+
+import org.apache.ignite.configuration.internal.DynamicConfiguration;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.FieldValidator;
+
+/**
+ * Validate that field value is not greater than some maximum value.
+ *
+ * @param <C> Root configuration type.
+ */
+public class MaxValidator<C extends DynamicConfiguration<?, ?, ?>> extends FieldValidator<Number, C> {
+    /** Maximum value. */
+    private final long maxValue;
+
+    /** Constructor. */
+    public MaxValidator(long maxValue, String message) {
+        super(message);
+        this.maxValue = maxValue;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void validate(Number value, C newRoot, C oldRoot) throws ConfigurationValidationException {
+        if (value.longValue() > maxValue)
+            throw new ConfigurationValidationException(message);
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MemberKey.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MemberKey.java
new file mode 100644
index 0000000..2462903
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MemberKey.java
@@ -0,0 +1,51 @@
+/*
+ * 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.internal.validation;
+
+import java.util.Objects;
+
+/**
+ * Configuration member key.
+ */
+public class MemberKey {
+    /** Class of the field holder. */
+    private final Class<?> clazz;
+
+    /** Name of the field. */
+    private final String fieldName;
+
+    /** Constructor. */
+    public MemberKey(Class<?> clazz, String fieldName) {
+        this.clazz = clazz;
+        this.fieldName = fieldName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        MemberKey key = (MemberKey) o;
+        return clazz.equals(key.clazz) &&
+                fieldName.equals(key.fieldName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        return Objects.hash(clazz, fieldName);
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MinValidator.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MinValidator.java
new file mode 100644
index 0000000..b94e65f
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/MinValidator.java
@@ -0,0 +1,44 @@
+/*
+ * 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.internal.validation;
+
+import org.apache.ignite.configuration.internal.DynamicConfiguration;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.FieldValidator;
+
+/**
+ * Validate that field value is not less than some minimal value.
+ *
+ * @param <C> Root configuration type.
+ */
+public class MinValidator<C extends DynamicConfiguration<?, ?, ?>> extends FieldValidator<Number, C> {
+    /** Minimal value. */
+    private final long minValue;
+
+    /** Constructor. */
+    public MinValidator(long minValue, String message) {
+        super(message);
+        this.minValue = minValue;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void validate(Number value, C newRoot, C oldRoot) throws ConfigurationValidationException {
+        if (value.longValue() < minValue)
+            throw new ConfigurationValidationException(message);
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/NotNullValidator.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/NotNullValidator.java
new file mode 100644
index 0000000..f060f53
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/internal/validation/NotNullValidator.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.internal.validation;
+
+import java.io.Serializable;
+import org.apache.ignite.configuration.internal.DynamicConfiguration;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.FieldValidator;
+
+/**
+ * Validate that field value is not null.
+ *
+ * @param <C> Root configuration type.
+ */
+public class NotNullValidator<C extends DynamicConfiguration<?, ?, ?>> extends FieldValidator<Serializable, C> {
+    /** Constructor. */
+    public NotNullValidator(String message) {
+        super(message);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void validate(Serializable value, C newRoot, C oldRoot) throws ConfigurationValidationException {
+        if (value == null)
+            throw new ConfigurationValidationException("");
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java
new file mode 100644
index 0000000..9cb37e4
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/ConfigurationStorage.java
@@ -0,0 +1,56 @@
+/*
+ * 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.storage;
+
+import java.io.Serializable;
+import java.util.function.Consumer;
+
+/**
+ * Storage interface for configuration.
+ */
+public interface ConfigurationStorage {
+    /**
+     * Save configuration property.
+     *
+     * @param propertyName Fully qualified name of the property.
+     * @param object Object, that represents the value of the property.
+     * @param <T> Type of the property.
+     * @throws StorageException If failed to save object.
+     */
+    <T extends Serializable> void save(String propertyName, T object) throws StorageException;
+
+    /**
+     * Get property value from storage.
+     *
+     * @param propertyName Fully qualified name of the property.
+     * @param <T> Type of the property.
+     * @return Object, that represents the value of the property.
+     * @throws StorageException If failed to retrieve object frm configuration storage.
+     */
+    <T extends Serializable> T get(String propertyName) throws StorageException;
+
+    /**
+     * Listen for the property change in the storage.
+     *
+     * @param key Key to listen on.
+     * @param listener Listener function.
+     * @param <T> Type of the property.
+     * @throws StorageException If failed to attach listener to configuration storage.
+     */
+    <T extends Serializable> void listen(String key, Consumer<T> listener) throws StorageException;
+
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/StorageException.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/StorageException.java
new file mode 100644
index 0000000..5c511db
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/storage/StorageException.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.configuration.storage;
+
+/**
+ * Exception thrown from configuration storage.
+ */
+public class StorageException extends RuntimeException {
+    /** Constructor. */
+    public StorageException() {
+    }
+
+    /** Constructor. */
+    public StorageException(String message) {
+        super(message);
+    }
+
+    /** Constructor. */
+    public StorageException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
new file mode 100644
index 0000000..a7860de
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.validation;
+
+/**
+ * Configuration validation exception.
+ */
+public class ConfigurationValidationException extends RuntimeException {
+
+    /** Constructor. */
+    public ConfigurationValidationException(String message) {
+        super(message);
+    }
+
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/FieldValidator.java b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/FieldValidator.java
new file mode 100644
index 0000000..f32a357
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/configuration/validation/FieldValidator.java
@@ -0,0 +1,47 @@
+/*
+ * 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.validation;
+
+import java.io.Serializable;
+import org.apache.ignite.configuration.ConfigurationTree;
+
+/**
+ * Base class for field validator. Contains exception message.
+ * @param <T> Field type.
+ * @param <C> Root configuration type.
+ */
+public abstract class FieldValidator<T extends Serializable, C extends ConfigurationTree<?, ?>> {
+    /** Validation error message. */
+    protected final String message;
+
+    /** Constructor. */
+    protected FieldValidator(String message) {
+        this.message = message;
+    }
+
+    /**
+     * Validate field.
+     *
+     * @param value New value.
+     * @param newRoot New configuration root.
+     * @param oldRoot Old configuration root.
+     * @throws ConfigurationValidationException If validation failed.
+     */
+    public abstract void validate(T value, C newRoot, C oldRoot) throws ConfigurationValidationException;
+
+}
diff --git a/pom.xml b/pom.xml
index e4d80c4..a27e525 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,4 +36,9 @@
             <scope>test</scope>
         </dependency>
     </dependencies>
+
+    <modules>
+        <module>modules/configuration</module>
+        <module>modules/configuration-annotation-processor</module>
+    </modules>
 </project>