You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by vv...@apache.org on 2022/06/02 17:09:13 UTC

[ignite-3] branch main updated: IGNITE-16943 Implement Micronaut REST (#816)

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

vveider 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 8e5410d5e IGNITE-16943 Implement Micronaut REST (#816)
8e5410d5e is described below

commit 8e5410d5e7bbb305b79758b80b21ff3e3799f7b4
Author: Alexandr <Sa...@icloud.com>
AuthorDate: Thu Jun 2 20:09:09 2022 +0300

    IGNITE-16943 Implement Micronaut REST (#816)
---
 .../org/apache/ignite/cli/ItConfigCommandTest.java |  21 +--
 .../apache/ignite/cli/CliPathsConfigLoader.java    |   4 +-
 .../java/org/apache/ignite/cli/CliVersionInfo.java |   2 +-
 .../java/org/apache/ignite/cli/ErrorHandler.java   |   2 +-
 .../org/apache/ignite/cli/VersionProvider.java     |   4 +-
 .../ignite/cli/builtins/SystemPathResolver.java    |   2 +-
 .../cli/builtins/cluster/ClusterApiClient.java     |   4 +-
 .../cli/builtins/config/ConfigurationClient.java   |   8 +-
 .../cli/builtins/config/HttpClientFactory.java     |   2 +-
 .../cli/builtins/init/InitIgniteCommand.java       |   2 +-
 .../cli/builtins/module/MavenArtifactResolver.java |   4 +-
 .../ignite/cli/builtins/module/ModuleManager.java  |   4 +-
 .../ignite/cli/builtins/module/ModuleRegistry.java |   4 +-
 .../ignite/cli/builtins/node/NodeManager.java      |   4 +-
 .../apache/ignite/cli/spec/ClusterCommandSpec.java |   2 +-
 .../apache/ignite/cli/spec/ConfigCommandSpec.java  |   2 +-
 .../org/apache/ignite/cli/spec/IgniteCliSpec.java  |   2 +-
 .../ignite/cli/spec/InitIgniteCommandSpec.java     |   2 +-
 .../apache/ignite/cli/spec/ModuleCommandSpec.java  |   2 +-
 .../apache/ignite/cli/spec/NodeCommandSpec.java    |   2 +-
 .../org/apache/ignite/cli/ui/TerminalFactory.java  |   2 +-
 .../apache/ignite/cli/IgniteCliInterfaceTest.java  |   8 +-
 .../cli/builtins/init/InitIgniteCommandTest.java   |   2 +-
 .../cli/builtins/module/ModuleMangerTest.java      |   2 +-
 modules/cluster-management/pom.xml                 |  36 +++-
 .../internal/cluster/management/MockNode.java      |  45 +++--
 .../rest/ItClusterManagementControllerTest.java    | 175 ++++++++++++++++++
 .../cluster/management/ClusterInitializer.java     |   2 +-
 .../management/ClusterManagementGroupManager.java  |  14 --
 .../rest/ClusterManagementController.java          |  93 ++++++++++
 .../rest/ClusterManagementRestFactory.java}        |  34 ++--
 .../management/rest/InitCommandHandler.java        |  94 ----------
 ...lidArgumentClusterInitializationException.java} |  17 +-
 .../handler/IgniteInternalExceptionHandler.java    |  42 +++++
 ...umentClusterInitializationExceptionHandler.java |  43 +++++
 modules/configuration/pom.xml                      |  35 ++++
 .../rest/ConfigurationHttpHandlers.java            | 175 ------------------
 .../AbstractConfigurationController.java           |  81 +++++++++
 .../ClusterConfigurationController.java            |  98 +++++++++++
 .../configuration/NodeConfigurationController.java |  98 +++++++++++
 .../rest/configuration/PresentationsFactory.java   |  55 ++++++
 .../ConfigPathUnrecognizedException.java}          |  17 +-
 .../exception/InvalidConfigFormatException.java}   |  11 +-
 .../ConfigPathUnrecognizedExceptionHandler.java    |  44 +++++
 .../ConfigurationValidationExceptionHandler.java   |  50 ++++++
 .../exception/handler/IgniteExceptionHandler.java  |  43 +++++
 .../InvalidConfigFormatExceptionHandler.java       |  43 +++++
 .../exception/handler}/package-info.java           |   5 +-
 .../configuration/exception}/package-info.java     |   4 +-
 .../ClusterConfigurationControllerTest.java        |  55 ++++++
 .../ConfigurationControllerBaseTest.java           | 145 +++++++++++++++
 .../NodeConfigurationControllerTest.java           |  55 ++++++
 .../internal/rest/configuration/TestFactory.java   |  70 ++++++++
 .../TestRootConfigurationSchema.java}              |  27 +--
 .../configuration/TestSubConfigurationSchema.java} |  15 +-
 modules/rest-api/pom.xml                           |  35 +---
 .../ignite/internal/rest/api/ErrorResult.java      |   9 +-
 .../ignite/internal/rest/api/RequestHandler.java   |  35 ----
 .../internal/rest/api/RestApiHttpRequest.java      |  62 -------
 .../internal/rest/api/RestApiHttpResponse.java     | 150 ----------------
 .../ignite/internal/rest/api/RestFactory.java}     |   8 +-
 .../org/apache/ignite/internal/rest/api/Route.java | 161 -----------------
 .../apache/ignite/internal/rest/api/Routes.java    |  96 ----------
 .../ignite/internal/rest/api}/package-info.java    |   4 +-
 .../internal/rest/api/RestApiHttpResponseTest.java |  53 ------
 modules/rest/openapi/openapi.yaml                  | 145 +++++++++++++++
 modules/rest/pom.xml                               |  56 +++---
 .../apache/ignite/internal/rest/RestComponent.java | 195 +++++++++++----------
 .../ignite/internal/rest/netty/RestApiHandler.java | 126 -------------
 .../internal/rest/netty/RestApiInitializer.java    |  51 ------
 .../apache/ignite/internal/rest/package-info.java  |   2 +-
 .../ignite/internal/rest/routes/SimpleRouter.java  |  71 --------
 .../ignite/internal/rest/routes/RouteTest.java     |  93 ----------
 .../ItDistributedConfigurationPropertiesTest.java  |   3 -
 .../ItDistributedConfigurationStorageTest.java     |   3 -
 .../runner/app/ItIgniteNodeRestartTest.java        |   2 -
 .../internal/runner/app/ItNoThreadsLeftTest.java   |   2 +-
 .../org/apache/ignite/internal/app/IgniteImpl.java |  19 +-
 parent/pom.xml                                     |  65 ++++++-
 79 files changed, 1774 insertions(+), 1486 deletions(-)

diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItConfigCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItConfigCommandTest.java
index d97b084b7..222876d18 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItConfigCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/ItConfigCommandTest.java
@@ -26,17 +26,12 @@ import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.startsWith;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 
-import com.jayway.jsonpath.DocumentContext;
-import com.jayway.jsonpath.JsonPath;
 import io.micronaut.context.ApplicationContext;
 import io.micronaut.context.env.Environment;
 import java.nio.file.Path;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
-import net.minidev.json.JSONObject;
-import net.minidev.json.JSONValue;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgnitionManager;
 import org.apache.ignite.internal.app.IgniteImpl;
@@ -113,9 +108,10 @@ public class ItConfigCommandTest extends AbstractCliIntegrationTest {
 
         assertEquals(0, exitCode);
 
-        DocumentContext document = JsonPath.parse(removeTrailingQuotes(unescapeQuotes(out.toString(UTF_8))));
-
-        assertEquals(1, document.read("$.network.shutdownQuietPeriod", Integer.class));
+        assertThat(
+                out.toString(UTF_8),
+                containsString("\"shutdownQuietPeriod\" : 1")
+        );
     }
 
     @Test
@@ -169,11 +165,12 @@ public class ItConfigCommandTest extends AbstractCliIntegrationTest {
 
         assertEquals(0, exitCode);
 
-        JSONObject outResult = (JSONObject) JSONValue.parse(removeTrailingQuotes(unescapeQuotes(out.toString(UTF_8))));
-
-        assertTrue(outResult.containsKey("inbound"));
+        assertThat(
+                out.toString(UTF_8),
+                containsString("\"inbound\"")
+        );
 
-        assertFalse(outResult.containsKey("node"));
+        assertFalse(out.toString(UTF_8).contains("\"node\""));
     }
 
     /**
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/CliPathsConfigLoader.java b/modules/cli/src/main/java/org/apache/ignite/cli/CliPathsConfigLoader.java
index 3233540d9..45ad77c21 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/CliPathsConfigLoader.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/CliPathsConfigLoader.java
@@ -17,14 +17,14 @@
 
 package org.apache.ignite.cli;
 
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Path;
 import java.util.Optional;
 import java.util.Properties;
-import javax.inject.Inject;
-import javax.inject.Singleton;
 import org.apache.ignite.cli.builtins.SystemPathResolver;
 
 /**
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/CliVersionInfo.java b/modules/cli/src/main/java/org/apache/ignite/cli/CliVersionInfo.java
index 30f1e3efb..987b05766 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/CliVersionInfo.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/CliVersionInfo.java
@@ -17,10 +17,10 @@
 
 package org.apache.ignite.cli;
 
+import jakarta.inject.Singleton;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Properties;
-import javax.inject.Singleton;
 
 /**
  * Provider of current Ignite CLI version info from the builtin properties file.
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/ErrorHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/ErrorHandler.java
index 7d2cb86e0..aebb5deb3 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/ErrorHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/ErrorHandler.java
@@ -17,7 +17,7 @@
 
 package org.apache.ignite.cli;
 
-import javax.inject.Singleton;
+import jakarta.inject.Singleton;
 import org.apache.ignite.cli.spec.CategorySpec;
 import org.apache.ignite.lang.IgniteLogger;
 import picocli.CommandLine;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java b/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
index 27b87b2e0..9099a22db 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/VersionProvider.java
@@ -18,8 +18,8 @@
 package org.apache.ignite.cli;
 
 import io.micronaut.core.annotation.Introspected;
-import javax.inject.Inject;
-import javax.inject.Singleton;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
 import picocli.CommandLine;
 
 /**
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/SystemPathResolver.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/SystemPathResolver.java
index 9153b024a..5b9768eb3 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/SystemPathResolver.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/SystemPathResolver.java
@@ -18,10 +18,10 @@
 package org.apache.ignite.cli.builtins;
 
 import io.micronaut.core.annotation.Introspected;
+import jakarta.inject.Singleton;
 import java.io.File;
 import java.net.URISyntaxException;
 import java.nio.file.Path;
-import javax.inject.Singleton;
 import org.apache.ignite.cli.IgniteCliApp;
 import org.apache.ignite.cli.IgniteCliException;
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/cluster/ClusterApiClient.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/cluster/ClusterApiClient.java
index 5c040f120..b6a5f29c2 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/cluster/ClusterApiClient.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/cluster/ClusterApiClient.java
@@ -20,6 +20,8 @@ package org.apache.ignite.cli.builtins.cluster;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.HttpURLConnection;
@@ -30,8 +32,6 @@ import java.net.http.HttpRequest.BodyPublishers;
 import java.net.http.HttpResponse;
 import java.net.http.HttpResponse.BodyHandlers;
 import java.util.List;
-import javax.inject.Inject;
-import javax.inject.Singleton;
 import org.apache.ignite.cli.IgniteCliException;
 
 /**
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/ConfigurationClient.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/ConfigurationClient.java
index f48053947..e08962082 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/ConfigurationClient.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/ConfigurationClient.java
@@ -22,6 +22,8 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.typesafe.config.ConfigFactory;
 import com.typesafe.config.ConfigRenderOptions;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.HttpURLConnection;
@@ -29,8 +31,6 @@ import java.net.URI;
 import java.net.http.HttpClient;
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
-import javax.inject.Inject;
-import javax.inject.Singleton;
 import org.apache.ignite.cli.IgniteCliException;
 import org.jetbrains.annotations.Nullable;
 import picocli.CommandLine.Help.ColorScheme;
@@ -81,7 +81,7 @@ public class ConfigurationClient {
     ) {
         var req = HttpRequest
                 .newBuilder()
-                .header("Content-Type", "application/json");
+                .header("Content-Type", "text/plain");
 
         if (rawHoconPath == null) {
             req.uri(URI.create("http://" + host + ":" + port + GET_URL + type + "/"));
@@ -120,7 +120,7 @@ public class ConfigurationClient {
         var req = HttpRequest
                 .newBuilder()
                 .method("PATCH", HttpRequest.BodyPublishers.ofString(renderJsonFromHocon(rawHoconData)))
-                .header("Content-Type", "application/json")
+                .header("Content-Type", "text/plain")
                 .uri(URI.create("http://" + host + ":" + port + PATCH_URL + type + "/"))
                 .build();
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java
index d123df4b3..178725423 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/config/HttpClientFactory.java
@@ -18,8 +18,8 @@
 package org.apache.ignite.cli.builtins.config;
 
 import io.micronaut.context.annotation.Factory;
+import jakarta.inject.Singleton;
 import java.net.http.HttpClient;
-import javax.inject.Singleton;
 
 /**
  * Factory for producing simple HTTP clients.
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/init/InitIgniteCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/init/InitIgniteCommand.java
index 64e9446a4..a45818545 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/init/InitIgniteCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/init/InitIgniteCommand.java
@@ -19,6 +19,7 @@ package org.apache.ignite.cli.builtins.init;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import jakarta.inject.Inject;
 import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
@@ -30,7 +31,6 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.Optional;
 import java.util.Properties;
-import javax.inject.Inject;
 import org.apache.ignite.cli.CliPathsConfigLoader;
 import org.apache.ignite.cli.IgniteCliException;
 import org.apache.ignite.cli.IgnitePaths;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/MavenArtifactResolver.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/MavenArtifactResolver.java
index 03182511a..324e5b087 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/MavenArtifactResolver.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/MavenArtifactResolver.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.cli.builtins.module;
 
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -26,8 +28,6 @@ import java.nio.file.Path;
 import java.text.ParseException;
 import java.util.List;
 import java.util.stream.Collectors;
-import javax.inject.Inject;
-import javax.inject.Singleton;
 import org.apache.ignite.cli.IgniteCliException;
 import org.apache.ignite.cli.builtins.SystemPathResolver;
 import org.apache.ignite.cli.ui.ProgressBar;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ModuleManager.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ModuleManager.java
index 1959aaab4..19203a9d1 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ModuleManager.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ModuleManager.java
@@ -20,6 +20,8 @@ package org.apache.ignite.cli.builtins.module;
 import com.typesafe.config.ConfigFactory;
 import com.typesafe.config.ConfigObject;
 import com.typesafe.config.ConfigValue;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -31,8 +33,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.jar.JarInputStream;
 import java.util.stream.Collectors;
-import javax.inject.Inject;
-import javax.inject.Singleton;
 import org.apache.ignite.cli.CliVersionInfo;
 import org.apache.ignite.cli.IgniteCliException;
 import org.apache.ignite.cli.IgnitePaths;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ModuleRegistry.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ModuleRegistry.java
index 1010af25c..9cf8104bb 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ModuleRegistry.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/module/ModuleRegistry.java
@@ -21,13 +21,13 @@ import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonGetter;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
-import javax.inject.Inject;
-import javax.inject.Singleton;
 import org.apache.ignite.cli.CliPathsConfigLoader;
 import org.apache.ignite.cli.IgniteCliException;
 import org.apache.ignite.internal.tostring.IgniteToStringInclude;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/node/NodeManager.java b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/node/NodeManager.java
index 6e2887af7..c02204163 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/builtins/node/NodeManager.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/builtins/node/NodeManager.java
@@ -19,6 +19,8 @@ package org.apache.ignite.cli.builtins.node;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -32,8 +34,6 @@ import java.util.Optional;
 import java.util.concurrent.locks.LockSupport;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
-import javax.inject.Inject;
-import javax.inject.Singleton;
 import org.apache.ignite.cli.IgniteCliException;
 import org.apache.ignite.cli.builtins.module.ModuleRegistry;
 import org.apache.ignite.cli.ui.Spinner;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/ClusterCommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/spec/ClusterCommandSpec.java
index 683eecfe4..acbf26fe1 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/ClusterCommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/spec/ClusterCommandSpec.java
@@ -17,9 +17,9 @@
 
 package org.apache.ignite.cli.spec;
 
+import jakarta.inject.Inject;
 import java.util.ArrayList;
 import java.util.List;
-import javax.inject.Inject;
 import org.apache.ignite.cli.builtins.cluster.ClusterApiClient;
 import picocli.CommandLine;
 import picocli.CommandLine.Option;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/ConfigCommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/spec/ConfigCommandSpec.java
index e8c23c156..7c34903c1 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/ConfigCommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/spec/ConfigCommandSpec.java
@@ -17,7 +17,7 @@
 
 package org.apache.ignite.cli.spec;
 
-import javax.inject.Inject;
+import jakarta.inject.Inject;
 import org.apache.ignite.cli.builtins.config.ConfigurationClient;
 import picocli.CommandLine;
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/IgniteCliSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/spec/IgniteCliSpec.java
index 53c1f83b7..063d2ed72 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/IgniteCliSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/spec/IgniteCliSpec.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.cli.spec;
 
 import io.micronaut.context.ApplicationContext;
+import jakarta.inject.Inject;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLClassLoader;
@@ -25,7 +26,6 @@ import java.nio.file.Path;
 import java.util.List;
 import java.util.ServiceLoader;
 import java.util.stream.Collectors;
-import javax.inject.Inject;
 import org.apache.ignite.cli.CliPathsConfigLoader;
 import org.apache.ignite.cli.CommandFactory;
 import org.apache.ignite.cli.ErrorHandler;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/InitIgniteCommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/spec/InitIgniteCommandSpec.java
index d6dec52d0..cb21e9e86 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/InitIgniteCommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/spec/InitIgniteCommandSpec.java
@@ -17,8 +17,8 @@
 
 package org.apache.ignite.cli.spec;
 
+import jakarta.inject.Inject;
 import java.net.URL;
-import javax.inject.Inject;
 import org.apache.ignite.cli.builtins.init.InitIgniteCommand;
 import org.apache.ignite.cli.common.IgniteCommand;
 import picocli.CommandLine;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/ModuleCommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/spec/ModuleCommandSpec.java
index c4f5b616a..92d87d109 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/ModuleCommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/spec/ModuleCommandSpec.java
@@ -17,13 +17,13 @@
 
 package org.apache.ignite.cli.spec;
 
+import jakarta.inject.Inject;
 import java.io.PrintWriter;
 import java.net.URL;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.stream.Collectors;
-import javax.inject.Inject;
 import org.apache.ignite.cli.CliPathsConfigLoader;
 import org.apache.ignite.cli.Table;
 import org.apache.ignite.cli.builtins.module.MavenCoordinates;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/spec/NodeCommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/spec/NodeCommandSpec.java
index f699f4276..bcbdd7ce0 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/spec/NodeCommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/spec/NodeCommandSpec.java
@@ -17,11 +17,11 @@
 
 package org.apache.ignite.cli.spec;
 
+import jakarta.inject.Inject;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.nio.file.Path;
 import java.util.List;
-import javax.inject.Inject;
 import org.apache.ignite.cli.CliPathsConfigLoader;
 import org.apache.ignite.cli.IgniteCliException;
 import org.apache.ignite.cli.IgnitePaths;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/ui/TerminalFactory.java b/modules/cli/src/main/java/org/apache/ignite/cli/ui/TerminalFactory.java
index db9c3bd21..3f1f38829 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/ui/TerminalFactory.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/ui/TerminalFactory.java
@@ -19,8 +19,8 @@ package org.apache.ignite.cli.ui;
 
 import io.micronaut.context.annotation.Bean;
 import io.micronaut.context.annotation.Factory;
+import jakarta.inject.Singleton;
 import java.io.IOException;
-import javax.inject.Singleton;
 import org.jline.terminal.Terminal;
 import org.jline.terminal.TerminalBuilder;
 
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/IgniteCliInterfaceTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/IgniteCliInterfaceTest.java
index c1f08f04d..384461f85 100644
--- a/modules/cli/src/test/java/org/apache/ignite/cli/IgniteCliInterfaceTest.java
+++ b/modules/cli/src/test/java/org/apache/ignite/cli/IgniteCliInterfaceTest.java
@@ -529,7 +529,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
 
             assertThat(capturedRequest.uri().toString(), is("http://localhost:8081/management/v1/configuration/node/"));
             assertThat(capturedRequest.method(), is("GET"));
-            assertThat(capturedRequest.headers().firstValue("Content-Type"), isPresentAnd(is("application/json")));
+            assertThat(capturedRequest.headers().firstValue("Content-Type"), isPresentAnd(is("text/plain")));
 
             assertOutputEqual("{\n"
                     + "  \"baseline\" : {\n"
@@ -560,7 +560,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
 
             assertThat(capturedRequest.uri().toString(), is("http://localhost:8081/management/v1/configuration/node/local.baseline"));
             assertThat(capturedRequest.method(), is("GET"));
-            assertThat(capturedRequest.headers().firstValue("Content-Type"), isPresentAnd(is("application/json")));
+            assertThat(capturedRequest.headers().firstValue("Content-Type"), isPresentAnd(is("text/plain")));
 
             assertOutputEqual("{\n"
                     + "  \"autoAdjust\" : {\n"
@@ -593,7 +593,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
             assertThat(capturedRequest.uri().toString(), is("http://localhost:8081/management/v1/configuration/node/"));
             assertThat(capturedRequest.method(), is("PATCH"));
             assertThat(requestBodyBytes(capturedRequest), is(expSentContent.getBytes(UTF_8)));
-            assertThat(capturedRequest.headers().firstValue("Content-Type"), isPresentAnd(is("application/json")));
+            assertThat(capturedRequest.headers().firstValue("Content-Type"), isPresentAnd(is("text/plain")));
 
             assertOutputEqual("Configuration was updated successfully.\n\n"
                     + "Use the " + cmd.getColorScheme().commandText("ignite config get")
@@ -627,7 +627,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
             assertThat(capturedRequest.uri().toString(), is("http://localhost:8081/management/v1/configuration/node/"));
             assertThat(capturedRequest.method(), is("PATCH"));
             assertThat(requestBodyBytes(capturedRequest), is(expSentContent.getBytes(UTF_8)));
-            assertThat(capturedRequest.headers().firstValue("Content-Type"), isPresentAnd(is("application/json")));
+            assertThat(capturedRequest.headers().firstValue("Content-Type"), isPresentAnd(is("text/plain")));
 
             assertOutputEqual("Configuration was updated successfully.\n\n"
                     + "Use the " + cmd.getColorScheme().commandText("ignite config get")
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/builtins/init/InitIgniteCommandTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/builtins/init/InitIgniteCommandTest.java
index 1fa33b742..fbf2a8f27 100644
--- a/modules/cli/src/test/java/org/apache/ignite/cli/builtins/init/InitIgniteCommandTest.java
+++ b/modules/cli/src/test/java/org/apache/ignite/cli/builtins/init/InitIgniteCommandTest.java
@@ -25,6 +25,7 @@ import static org.mockito.Mockito.when;
 
 import io.micronaut.test.annotation.MockBean;
 import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -32,7 +33,6 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Collections;
 import java.util.Comparator;
-import javax.inject.Inject;
 import org.apache.ignite.cli.AbstractCliTest;
 import org.apache.ignite.cli.CliPathsConfigLoader;
 import org.apache.ignite.cli.builtins.SystemPathResolver;
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/builtins/module/ModuleMangerTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/builtins/module/ModuleMangerTest.java
index 359e3ca96..9f41da99d 100644
--- a/modules/cli/src/test/java/org/apache/ignite/cli/builtins/module/ModuleMangerTest.java
+++ b/modules/cli/src/test/java/org/apache/ignite/cli/builtins/module/ModuleMangerTest.java
@@ -25,6 +25,7 @@ import static org.mockito.Mockito.when;
 
 import io.micronaut.test.annotation.MockBean;
 import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -34,7 +35,6 @@ import java.util.Collections;
 import java.util.jar.Attributes;
 import java.util.jar.JarOutputStream;
 import java.util.jar.Manifest;
-import javax.inject.Inject;
 import org.apache.ignite.cli.AbstractCliTest;
 import org.apache.ignite.cli.IgnitePaths;
 import org.junit.jupiter.api.Test;
diff --git a/modules/cluster-management/pom.xml b/modules/cluster-management/pom.xml
index 405e4ad3c..250de9423 100644
--- a/modules/cluster-management/pom.xml
+++ b/modules/cluster-management/pom.xml
@@ -49,17 +49,22 @@
 
         <dependency>
             <groupId>org.apache.ignite</groupId>
-            <artifactId>ignite-rest</artifactId>
+            <artifactId>ignite-vault</artifactId>
         </dependency>
 
         <dependency>
             <groupId>org.apache.ignite</groupId>
-            <artifactId>ignite-vault</artifactId>
+            <artifactId>ignite-rest-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.micronaut</groupId>
+            <artifactId>micronaut-http</artifactId>
         </dependency>
 
         <dependency>
-            <groupId>com.fasterxml.jackson.core</groupId>
-            <artifactId>jackson-databind</artifactId>
+            <groupId>io.micronaut</groupId>
+            <artifactId>micronaut-http-server</artifactId>
         </dependency>
 
         <!-- Test dependencies -->
@@ -113,6 +118,24 @@
             <artifactId>slf4j-jdk14</artifactId>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>io.micronaut.test</groupId>
+            <artifactId>micronaut-test-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>io.micronaut</groupId>
+            <artifactId>micronaut-http-client</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>io.micronaut</groupId>
+            <artifactId>micronaut-http-server-netty</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
@@ -134,6 +157,11 @@
                             <artifactId>ignite-network-annotation-processor</artifactId>
                             <version>${project.version}</version>
                         </path>
+                        <path>
+                            <groupId>io.micronaut</groupId>
+                            <artifactId>micronaut-inject-java</artifactId>
+                            <version>${micronaut.version}</version>
+                        </path>
                     </annotationProcessorPaths>
                 </configuration>
             </plugin>
diff --git a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/MockNode.java b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/MockNode.java
index 2199f6268..4be44456e 100644
--- a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/MockNode.java
+++ b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/MockNode.java
@@ -17,7 +17,6 @@
 
 package org.apache.ignite.internal.cluster.management;
 
-import static org.mockito.Mockito.mock;
 
 import java.io.IOException;
 import java.nio.file.Files;
@@ -28,7 +27,6 @@ import java.util.concurrent.CompletableFuture;
 import org.apache.ignite.internal.cluster.management.raft.RocksDbClusterStateStorage;
 import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.internal.raft.Loza;
-import org.apache.ignite.internal.rest.RestComponent;
 import org.apache.ignite.internal.util.ReverseIterator;
 import org.apache.ignite.internal.vault.VaultManager;
 import org.apache.ignite.internal.vault.persistence.PersistentVaultService;
@@ -42,7 +40,7 @@ import org.junit.jupiter.api.TestInfo;
 /**
  * Fake node for integration tests.
  */
-class MockNode {
+public class MockNode {
     private ClusterManagementGroupManager clusterManager;
 
     private ClusterService clusterService;
@@ -57,7 +55,10 @@ class MockNode {
 
     private CompletableFuture<Void> startFuture;
 
-    MockNode(TestInfo testInfo, NetworkAddress addr, NodeFinder nodeFinder, Path workDir) throws IOException {
+    /**
+     * Fake node constructor.
+     */
+    public MockNode(TestInfo testInfo, NetworkAddress addr, NodeFinder nodeFinder, Path workDir) throws IOException {
         this.testInfo = testInfo;
         this.nodeFinder = nodeFinder;
         this.workDir = workDir;
@@ -78,7 +79,6 @@ class MockNode {
                 vaultManager,
                 clusterService,
                 raftManager,
-                mock(RestComponent.class),
                 new RocksDbClusterStateStorage(workDir.resolve("cmg"))
         );
 
@@ -88,23 +88,35 @@ class MockNode {
         components.add(clusterManager);
     }
 
-    void startComponents() {
+    /**
+     * Start fake node.
+     */
+    public void startComponents() {
         components.forEach(IgniteComponent::start);
     }
 
-    void start() {
+    /**
+     * Start fake node.
+     */
+    public void start() {
         startComponents();
 
         startFuture = clusterManager.onJoinReady();
     }
 
-    void beforeNodeStop() {
+    /**
+     * Method should be called before node stop.
+     */
+    public void beforeNodeStop() {
         ReverseIterator<IgniteComponent> it = new ReverseIterator<>(components);
 
         it.forEachRemaining(IgniteComponent::beforeNodeStop);
     }
 
-    void stop() {
+    /**
+     * Stop fake node.
+     */
+    public void stop() {
         ReverseIterator<IgniteComponent> it = new ReverseIterator<>(components);
 
         it.forEachRemaining(component -> {
@@ -116,7 +128,10 @@ class MockNode {
         });
     }
 
-    void restart() throws Exception {
+    /**
+     * Restart fake node.
+     */
+    public void restart() throws Exception {
         int port = localMember().address().port();
 
         beforeNodeStop();
@@ -129,15 +144,19 @@ class MockNode {
         start();
     }
 
-    ClusterNode localMember() {
+    public ClusterNode localMember() {
         return clusterService.topologyService().localMember();
     }
 
-    ClusterManagementGroupManager clusterManager() {
+    public ClusterManagementGroupManager clusterManager() {
         return clusterManager;
     }
 
-    CompletableFuture<Void> startFuture() {
+    public CompletableFuture<Void> startFuture() {
         return startFuture;
     }
+
+    public ClusterService clusterService() {
+        return clusterService;
+    }
 }
diff --git a/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/rest/ItClusterManagementControllerTest.java b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/rest/ItClusterManagementControllerTest.java
new file mode 100644
index 000000000..7e02245ed
--- /dev/null
+++ b/modules/cluster-management/src/integrationTest/java/org/apache/ignite/internal/cluster/management/rest/ItClusterManagementControllerTest.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cluster.management.rest;
+
+import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.context.annotation.Factory;
+import io.micronaut.context.annotation.Replaces;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.HttpStatus;
+import io.micronaut.http.client.HttpClient;
+import io.micronaut.http.client.annotation.Client;
+import io.micronaut.http.client.exceptions.HttpClientResponseException;
+import io.micronaut.runtime.server.EmbeddedServer;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.ignite.internal.cluster.management.MockNode;
+import org.apache.ignite.internal.rest.api.ErrorResult;
+import org.apache.ignite.internal.testframework.WorkDirectory;
+import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
+import org.apache.ignite.network.ClusterService;
+import org.apache.ignite.network.NetworkAddress;
+import org.apache.ignite.network.StaticNodeFinder;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+/**
+ * Cluster management REST test.
+ */
+@MicronautTest
+@ExtendWith(WorkDirectoryExtension.class)
+public class ItClusterManagementControllerTest {
+
+    private static final int PORT_BASE = 10000;
+
+    private static final List<MockNode> cluster = new ArrayList<>();
+
+    static ClusterService clusterService;
+
+    @WorkDirectory
+    private static Path workDir;
+
+    @Inject
+    private EmbeddedServer server;
+
+    @Inject
+    @Client("/management/v1/cluster/init/")
+    private HttpClient client;
+
+    @BeforeAll
+    static void setUp(TestInfo testInfo) throws IOException {
+        var addr1 = new NetworkAddress("localhost", PORT_BASE);
+        var addr2 = new NetworkAddress("localhost", PORT_BASE + 1);
+
+        var nodeFinder = new StaticNodeFinder(List.of(addr1, addr2));
+
+        cluster.add(new MockNode(testInfo, addr1, nodeFinder, workDir.resolve("node0")));
+        cluster.add(new MockNode(testInfo, addr2, nodeFinder, workDir.resolve("node1")));
+
+        for (MockNode node : cluster) {
+            node.start();
+        }
+
+        clusterService = cluster.get(0).clusterService();
+    }
+
+    @AfterAll
+    static void tearDown() {
+        for (MockNode node : cluster) {
+            node.beforeNodeStop();
+        }
+
+        for (MockNode node : cluster) {
+            node.stop();
+        }
+    }
+
+    @Test
+    void testControllerLoaded() {
+        assertNotNull(server.getApplicationContext().getBean(ClusterManagementController.class));
+    }
+
+    @Test
+    void testInitNoSuchNode() {
+        // Given body with nodename that does not exist
+        String givenInvalidBody = "{\"metaStorageNodes\": [\"nodename\"], \"cmgNodes\": [], \"clusterName\": \"cluster\"}";
+
+        // When
+        var thrown = assertThrows(
+                HttpClientResponseException.class,
+                () -> client.toBlocking().exchange(HttpRequest.POST("", givenInvalidBody))
+        );
+
+        // Then
+        assertThat(thrown.getResponse().getStatus(), is(equalTo((HttpStatus.BAD_REQUEST))));
+        // And
+        var errorResult = getErrorResult(thrown);
+        assertEquals("INVALID_ARGUMENT", errorResult.type());
+    }
+
+    @Test
+    void testInitAlreadyInitializedWithAnotherNodes() {
+        // Given cluster initialized
+        String givenFirstRequestBody =
+                "{\"metaStorageNodes\": [\"" + cluster.get(0).clusterService().localConfiguration().getName() + "\"], \"cmgNodes\": [], "
+                        + "\"clusterName\": \"cluster\"}";
+
+        // When
+        HttpResponse<Object> response = client.toBlocking().exchange(HttpRequest.POST("", givenFirstRequestBody));
+
+        // Then
+        assertThat(response.getStatus(), is(equalTo((HttpStatus.OK))));
+        // And
+        assertThat(cluster.get(0).startFuture(), willCompleteSuccessfully());
+
+        // Given second request with different node name
+        String givenSecondRequestBody =
+                "{\"metaStorageNodes\": [\"" + cluster.get(1).clusterService().localConfiguration().getName() + "\"], \"cmgNodes\": [], "
+                        + "\"clusterName\": \"cluster\" }";
+
+        // When
+        var thrown = assertThrows(
+                HttpClientResponseException.class,
+                () -> client.toBlocking().exchange(HttpRequest.POST("", givenSecondRequestBody))
+        );
+
+        // Then
+        assertThat(thrown.getResponse().getStatus(), is(equalTo((HttpStatus.INTERNAL_SERVER_ERROR))));
+        // And
+        var errorResult = getErrorResult(thrown);
+        assertEquals("SERVER_ERROR", errorResult.type());
+
+    }
+
+    @Factory
+    @Bean
+    @Replaces(ClusterManagementRestFactory.class)
+    public ClusterManagementRestFactory clusterManagementRestFactory() {
+        return new ClusterManagementRestFactory(clusterService);
+    }
+
+    private ErrorResult getErrorResult(HttpClientResponseException exception) {
+        return exception.getResponse().getBody(ErrorResult.class).orElseThrow();
+    }
+}
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterInitializer.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterInitializer.java
index 42f6e4249..dafd51da7 100644
--- a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterInitializer.java
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterInitializer.java
@@ -48,7 +48,7 @@ public class ClusterInitializer {
     private final CmgMessagesFactory msgFactory = new CmgMessagesFactory();
 
     /** Constructor. */
-    ClusterInitializer(ClusterService clusterService) {
+    public ClusterInitializer(ClusterService clusterService) {
         this.clusterService = clusterService;
     }
 
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterManagementGroupManager.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterManagementGroupManager.java
index 3159300c3..8ea630f1c 100644
--- a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterManagementGroupManager.java
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/ClusterManagementGroupManager.java
@@ -17,7 +17,6 @@
 
 package org.apache.ignite.internal.cluster.management;
 
-import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON;
 import static java.util.concurrent.CompletableFuture.completedFuture;
 import static java.util.concurrent.CompletableFuture.failedFuture;
 import static java.util.stream.Collectors.toSet;
@@ -48,11 +47,9 @@ import org.apache.ignite.internal.cluster.management.raft.CmgRaftService;
 import org.apache.ignite.internal.cluster.management.raft.IllegalInitArgumentException;
 import org.apache.ignite.internal.cluster.management.raft.JoinDeniedException;
 import org.apache.ignite.internal.cluster.management.raft.commands.JoinReadyCommand;
-import org.apache.ignite.internal.cluster.management.rest.InitCommandHandler;
 import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.internal.properties.IgniteProductVersion;
 import org.apache.ignite.internal.raft.Loza;
-import org.apache.ignite.internal.rest.RestComponent;
 import org.apache.ignite.internal.thread.NamedThreadFactory;
 import org.apache.ignite.internal.util.IgniteSpinBusyLock;
 import org.apache.ignite.internal.util.IgniteUtils;
@@ -85,9 +82,6 @@ public class ClusterManagementGroupManager implements IgniteComponent {
     /** CMG Raft group name. */
     private static final String CMG_RAFT_GROUP_NAME = "cmg_raft_group";
 
-    /** Init REST endpoint path. */
-    private static final String REST_ENDPOINT = "/management/v1/cluster/init";
-
     /** Busy lock to stop synchronously. */
     private final IgniteSpinBusyLock busyLock = new IgniteSpinBusyLock();
 
@@ -117,8 +111,6 @@ public class ClusterManagementGroupManager implements IgniteComponent {
 
     private final Loza raftManager;
 
-    private final RestComponent restComponent;
-
     private final ClusterStateStorage clusterStateStorage;
 
     /** Local state. */
@@ -132,12 +124,10 @@ public class ClusterManagementGroupManager implements IgniteComponent {
             VaultManager vault,
             ClusterService clusterService,
             Loza raftManager,
-            RestComponent restComponent,
             ClusterStateStorage clusterStateStorage
     ) {
         this.clusterService = clusterService;
         this.raftManager = raftManager;
-        this.restComponent = restComponent;
         this.clusterStateStorage = clusterStateStorage;
         this.localStateStorage = new LocalStateStorage(vault);
         this.clusterInitializer = new ClusterInitializer(clusterService);
@@ -196,10 +186,6 @@ public class ClusterManagementGroupManager implements IgniteComponent {
                     }
                 })
         );
-
-        restComponent.registerHandlers(routes ->
-                routes.post(REST_ENDPOINT, APPLICATION_JSON.toString(), new InitCommandHandler(clusterInitializer))
-        );
     }
 
     /**
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementController.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementController.java
new file mode 100644
index 000000000..2f15425af
--- /dev/null
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementController.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cluster.management.rest;
+
+import io.micronaut.http.MediaType;
+import io.micronaut.http.annotation.Body;
+import io.micronaut.http.annotation.Consumes;
+import io.micronaut.http.annotation.Controller;
+import io.micronaut.http.annotation.Post;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import org.apache.ignite.internal.cluster.management.ClusterInitializer;
+import org.apache.ignite.internal.cluster.management.rest.exception.InvalidArgumentClusterInitializationException;
+import org.apache.ignite.lang.IgniteException;
+import org.apache.ignite.lang.IgniteInternalException;
+import org.apache.ignite.lang.IgniteLogger;
+
+/**
+ * Cluster management controller.
+ */
+@Controller("/management/v1/cluster/init")
+@ApiResponse(responseCode = "400", description = "Bad request")
+@ApiResponse(responseCode = "500", description = "Internal error")
+@Tag(name = "clusterManagement")
+public class ClusterManagementController {
+    private static final IgniteLogger log = IgniteLogger.forClass(ClusterManagementController.class);
+
+    private final ClusterInitializer clusterInitializer;
+
+    /**
+     * Cluster management controller constructor.
+     *
+     * @param clusterInitializer cluster initializer.
+     */
+    public ClusterManagementController(ClusterInitializer clusterInitializer) {
+        this.clusterInitializer = clusterInitializer;
+    }
+
+    /**
+     * Initializes cluster.
+     *
+     * @return Completable future that will be completed whet cluster is initialized.
+     */
+    @Post
+    @Operation(operationId = "init")
+    @ApiResponse(responseCode = "200", description = "Cluster initialized")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public CompletableFuture<Void> init(@Body InitCommand initCommand) throws ExecutionException, InterruptedException {
+        if (log.isInfoEnabled()) {
+            log.info("Received init command:\n\tMeta Storage nodes: {}\n\tCMG nodes: {}", initCommand.metaStorageNodes(),
+                    initCommand.cmgNodes());
+        }
+
+        return clusterInitializer.initCluster(initCommand.metaStorageNodes(), initCommand.cmgNodes(), initCommand.clusterName())
+                .exceptionally(ex -> {
+                    throw mapException(ex);
+                });
+    }
+
+    private RuntimeException mapException(Throwable ex) {
+        if (ex instanceof CompletionException) {
+            var cause = ex.getCause();
+            if (cause instanceof IgniteInternalException) {
+                return (IgniteInternalException) cause;
+            }
+        }
+
+        if (ex instanceof IllegalArgumentException) {
+            return new InvalidArgumentClusterInitializationException(ex);
+        }
+
+        return new IgniteException(ex);
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/ui/TerminalFactory.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementRestFactory.java
similarity index 56%
copy from modules/cli/src/main/java/org/apache/ignite/cli/ui/TerminalFactory.java
copy to modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementRestFactory.java
index db9c3bd21..6f8f1bd82 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/ui/TerminalFactory.java
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/ClusterManagementRestFactory.java
@@ -15,31 +15,29 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.ui;
+package org.apache.ignite.internal.cluster.management.rest;
 
 import io.micronaut.context.annotation.Bean;
 import io.micronaut.context.annotation.Factory;
-import java.io.IOException;
-import javax.inject.Singleton;
-import org.jline.terminal.Terminal;
-import org.jline.terminal.TerminalBuilder;
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.cluster.management.ClusterInitializer;
+import org.apache.ignite.internal.rest.api.RestFactory;
+import org.apache.ignite.network.ClusterService;
 
 /**
- * Factory for producing JLine {@link Terminal} instances.
+ * Factory that creates beans that are needed for {@link ClusterManagementController}.
  */
 @Factory
-public class TerminalFactory {
-    /**
-     * Produce terminal instances.
-     *
-     * <p>Important: It's always must be a singleton bean. JLine has an issues with building more than 1 terminal instance per process.
-     *
-     * @return Terminal instance.
-     * @throws IOException if an error occurs.
-     */
-    @Bean(preDestroy = "close")
+public class ClusterManagementRestFactory implements RestFactory {
+    private final ClusterService clusterService;
+
+    public ClusterManagementRestFactory(ClusterService clusterService) {
+        this.clusterService = clusterService;
+    }
+
+    @Bean
     @Singleton
-    public Terminal terminal() throws IOException {
-        return TerminalBuilder.terminal();
+    public ClusterInitializer clusterInitializer() {
+        return new ClusterInitializer(clusterService);
     }
 }
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/InitCommandHandler.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/InitCommandHandler.java
deleted file mode 100644
index 9737b894a..000000000
--- a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/InitCommandHandler.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.cluster.management.rest;
-
-import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
-import org.apache.ignite.internal.cluster.management.ClusterInitializer;
-import org.apache.ignite.internal.rest.api.ErrorResult;
-import org.apache.ignite.internal.rest.api.RequestHandler;
-import org.apache.ignite.internal.rest.api.RestApiHttpRequest;
-import org.apache.ignite.internal.rest.api.RestApiHttpResponse;
-import org.apache.ignite.lang.IgniteLogger;
-
-/**
- * REST handler for the {@link InitCommand}.
- */
-public class InitCommandHandler implements RequestHandler {
-    private static final IgniteLogger LOG = IgniteLogger.forClass(InitCommandHandler.class);
-
-    private final ObjectMapper objectMapper = new ObjectMapper();
-
-    private final ClusterInitializer clusterInitializer;
-
-    public InitCommandHandler(ClusterInitializer clusterInitializer) {
-        this.clusterInitializer = clusterInitializer;
-    }
-
-    @Override
-    public CompletableFuture<RestApiHttpResponse> handle(RestApiHttpRequest request, RestApiHttpResponse response) {
-        try {
-            InitCommand command = readContent(request);
-
-            if (LOG.isInfoEnabled()) {
-                LOG.info("Received init command:\n\t{}}", command);
-            }
-
-            return clusterInitializer.initCluster(command.metaStorageNodes(), command.cmgNodes(), command.clusterName())
-                    .thenApply(v -> successResponse(response))
-                    .exceptionally(e -> errorResponse(response, e));
-        } catch (Exception e) {
-            return CompletableFuture.completedFuture(errorResponse(response, e));
-        }
-    }
-
-    private InitCommand readContent(RestApiHttpRequest restApiHttpRequest) throws IOException {
-        ByteBuf content = restApiHttpRequest.request().content();
-
-        try (InputStream is = new ByteBufInputStream(content)) {
-            return objectMapper.readValue(is, InitCommand.class);
-        }
-    }
-
-    private static RestApiHttpResponse successResponse(RestApiHttpResponse response) {
-        LOG.info("Init command executed successfully");
-
-        return response;
-    }
-
-    private static RestApiHttpResponse errorResponse(RestApiHttpResponse response, Throwable e) {
-        if (e instanceof CompletionException) {
-            e = e.getCause();
-        }
-
-        LOG.error("Init command failure", e);
-
-        response.status(INTERNAL_SERVER_ERROR);
-        response.json(Map.of("error", new ErrorResult("APPLICATION_EXCEPTION", e.getMessage())));
-
-        return response;
-    }
-}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestHandlersRegister.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/InvalidArgumentClusterInitializationException.java
similarity index 69%
copy from modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestHandlersRegister.java
copy to modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/InvalidArgumentClusterInitializationException.java
index c55e7d6a7..f92daa173 100644
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestHandlersRegister.java
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/InvalidArgumentClusterInitializationException.java
@@ -15,18 +15,13 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.rest.api;
-
-import java.util.function.Consumer;
+package org.apache.ignite.internal.cluster.management.rest.exception;
 
 /**
- * Register for REST handlers.
+ * Exception that is thrown when the wrong arguments are passed to the init cluster method.
  */
-public interface RestHandlersRegister {
-    /**
-     * Registers handlers on the provided {@link Routes}.
-     *
-     * @param registerAction registration action
-     */
-    void registerHandlers(Consumer<Routes> registerAction);
+public class InvalidArgumentClusterInitializationException extends RuntimeException {
+    public InvalidArgumentClusterInitializationException(Throwable cause) {
+        super(cause);
+    }
 }
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/IgniteInternalExceptionHandler.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/IgniteInternalExceptionHandler.java
new file mode 100644
index 000000000..2f2e3f315
--- /dev/null
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/IgniteInternalExceptionHandler.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.internal.cluster.management.rest.exception.handler;
+
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.annotation.Produces;
+import io.micronaut.http.server.exceptions.ExceptionHandler;
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.rest.api.ErrorResult;
+import org.apache.ignite.lang.IgniteInternalException;
+
+/**
+ * Handles {@link IgniteInternalException} and represents it as a rest response.
+ */
+@Produces
+@Singleton
+@Requires(classes = {IgniteInternalException.class, ExceptionHandler.class})
+public class IgniteInternalExceptionHandler implements ExceptionHandler<IgniteInternalException, HttpResponse<ErrorResult>> {
+
+    @Override
+    public HttpResponse<ErrorResult> handle(HttpRequest request, IgniteInternalException exception) {
+        ErrorResult errorResult = new ErrorResult("SERVER_ERROR", exception.getMessage());
+        return HttpResponse.serverError().body(errorResult);
+    }
+}
diff --git a/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/InvalidArgumentClusterInitializationExceptionHandler.java b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/InvalidArgumentClusterInitializationExceptionHandler.java
new file mode 100644
index 000000000..d22774b41
--- /dev/null
+++ b/modules/cluster-management/src/main/java/org/apache/ignite/internal/cluster/management/rest/exception/handler/InvalidArgumentClusterInitializationExceptionHandler.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cluster.management.rest.exception.handler;
+
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.annotation.Produces;
+import io.micronaut.http.server.exceptions.ExceptionHandler;
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.cluster.management.rest.exception.InvalidArgumentClusterInitializationException;
+import org.apache.ignite.internal.rest.api.ErrorResult;
+
+/**
+ * Handles {@link InvalidArgumentClusterInitializationException} and represents it as a rest response.
+ */
+@Produces
+@Singleton
+@Requires(classes = {InvalidArgumentClusterInitializationException.class, ExceptionHandler.class})
+public class InvalidArgumentClusterInitializationExceptionHandler implements
+        ExceptionHandler<InvalidArgumentClusterInitializationException, HttpResponse<ErrorResult>> {
+
+    @Override
+    public HttpResponse<ErrorResult> handle(HttpRequest request, InvalidArgumentClusterInitializationException exception) {
+        ErrorResult errorResult = new ErrorResult("INVALID_ARGUMENT", exception.getCause().getMessage());
+        return HttpResponse.badRequest().body(errorResult);
+    }
+}
diff --git a/modules/configuration/pom.xml b/modules/configuration/pom.xml
index 1063894f6..d97d770f9 100644
--- a/modules/configuration/pom.xml
+++ b/modules/configuration/pom.xml
@@ -54,6 +54,16 @@
         </dependency>
 
         <!-- 3rd party dependencies -->
+        <dependency>
+            <groupId>io.micronaut</groupId>
+            <artifactId>micronaut-http</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.micronaut</groupId>
+            <artifactId>micronaut-http-server</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>com.typesafe</groupId>
             <artifactId>config</artifactId>
@@ -90,6 +100,26 @@
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
+
+
+        <dependency>
+            <groupId>io.micronaut.test</groupId>
+            <artifactId>micronaut-test-junit5</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>io.micronaut</groupId>
+            <artifactId>micronaut-http-client</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>io.micronaut</groupId>
+            <artifactId>micronaut-http-server-netty</artifactId>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 
     <build>
@@ -123,6 +153,11 @@
                             <artifactId>ignite-configuration-annotation-processor</artifactId>
                             <version>${project.version}</version>
                         </path>
+                        <path>
+                            <groupId>io.micronaut</groupId>
+                            <artifactId>micronaut-inject-java</artifactId>
+                            <version>${micronaut.version}</version>
+                        </path>
                     </annotationProcessorPaths>
                 </configuration>
             </plugin>
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/rest/ConfigurationHttpHandlers.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/rest/ConfigurationHttpHandlers.java
deleted file mode 100644
index 09e9dda4b..000000000
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/rest/ConfigurationHttpHandlers.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.configuration.rest;
-
-import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON;
-import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
-import org.apache.ignite.configuration.validation.ConfigurationValidationException;
-import org.apache.ignite.internal.configuration.ConfigurationManager;
-import org.apache.ignite.internal.configuration.rest.presentation.ConfigurationPresentation;
-import org.apache.ignite.internal.configuration.rest.presentation.hocon.HoconPresentation;
-import org.apache.ignite.internal.rest.api.ErrorResult;
-import org.apache.ignite.internal.rest.api.RestApiHttpRequest;
-import org.apache.ignite.internal.rest.api.RestApiHttpResponse;
-import org.apache.ignite.internal.rest.api.RestHandlersRegister;
-import org.apache.ignite.internal.rest.api.Routes;
-import org.apache.ignite.lang.IgniteException;
-
-/**
- * HTTP handlers for configuration-related REST endpoints.
- */
-public class ConfigurationHttpHandlers {
-    /**
-     * Node configuration route.
-     */
-    private static final String NODE_CFG_URL = "/management/v1/configuration/node/";
-
-    /**
-     * Cluster configuration route.
-     */
-    private static final String CLUSTER_CFG_URL = "/management/v1/configuration/cluster/";
-
-    /**
-     * Path parameter.
-     */
-    private static final String PATH_PARAM = "selector";
-
-    private final ConfigurationManager nodeConfigurationManager;
-    private final ConfigurationManager clusterConfigurationManager;
-
-    public ConfigurationHttpHandlers(ConfigurationManager nodeConfigurationManager, ConfigurationManager clusterConfigurationManager) {
-        this.nodeConfigurationManager = nodeConfigurationManager;
-        this.clusterConfigurationManager = clusterConfigurationManager;
-    }
-
-    public void registerHandlers(RestHandlersRegister register) {
-        register.registerHandlers(this::registerOn);
-    }
-
-    private void registerOn(Routes routes) {
-        var nodeCfgPresentation = new HoconPresentation(nodeConfigurationManager.configurationRegistry());
-        var clusterCfgPresentation = new HoconPresentation(clusterConfigurationManager.configurationRegistry());
-
-        routes
-                .get(
-                        NODE_CFG_URL,
-                        (req, resp) -> {
-                            resp.json(nodeCfgPresentation.represent());
-
-                            return CompletableFuture.completedFuture(resp);
-                        }
-                )
-                .get(
-                        CLUSTER_CFG_URL,
-                        (req, resp) -> {
-                            resp.json(clusterCfgPresentation.represent());
-
-                            return CompletableFuture.completedFuture(resp);
-                        }
-                )
-                .get(
-                        NODE_CFG_URL + ":" + PATH_PARAM,
-                        (req, resp) -> handleRepresentByPath(req, resp, nodeCfgPresentation)
-                )
-                .get(
-                        CLUSTER_CFG_URL + ":" + PATH_PARAM,
-                        (req, resp) -> handleRepresentByPath(req, resp, clusterCfgPresentation)
-                )
-                .patch(
-                        NODE_CFG_URL,
-                        APPLICATION_JSON.toString(),
-                        (req, resp) -> handleUpdate(req, resp, nodeCfgPresentation)
-                )
-                .patch(
-                        CLUSTER_CFG_URL,
-                        APPLICATION_JSON.toString(),
-                        (req, resp) -> handleUpdate(req, resp, clusterCfgPresentation)
-                );
-    }
-
-    /**
-     * Handle a request to get the configuration by {@link #PATH_PARAM path}.
-     *
-     * @param req          Rest request.
-     * @param res          Rest response.
-     * @param presentation Configuration presentation.
-     */
-    private static CompletableFuture<RestApiHttpResponse> handleRepresentByPath(
-            RestApiHttpRequest req,
-            RestApiHttpResponse res,
-            ConfigurationPresentation<String> presentation
-    ) {
-        try {
-            String cfgPath = req.queryParams().get(PATH_PARAM);
-
-            res.json(presentation.representByPath(cfgPath));
-        } catch (IllegalArgumentException pathE) {
-            ErrorResult errRes = new ErrorResult("CONFIG_PATH_UNRECOGNIZED", pathE.getMessage());
-
-            res.status(BAD_REQUEST);
-            res.json(Map.of("error", errRes));
-        }
-
-        return CompletableFuture.completedFuture(res);
-    }
-
-    /**
-     * Handle a configuration update request as json.
-     *
-     * @param req          Rest request.
-     * @param res          Rest response.
-     * @param presentation Configuration presentation.
-     */
-    private static CompletableFuture<RestApiHttpResponse> handleUpdate(
-            RestApiHttpRequest req,
-            RestApiHttpResponse res,
-            ConfigurationPresentation<String> presentation
-    ) {
-        String updateReq = req.request().content().toString(StandardCharsets.UTF_8);
-
-        return presentation.update(updateReq)
-                .thenApply(v -> res)
-                .exceptionally(e -> {
-                    if (e instanceof CompletionException) {
-                        e = e.getCause();
-                    }
-
-                    ErrorResult errRes;
-
-                    if (e instanceof IllegalArgumentException) {
-                        errRes = new ErrorResult("INVALID_CONFIG_FORMAT", e.getMessage());
-                    } else if (e instanceof ConfigurationValidationException) {
-                        errRes = new ErrorResult("VALIDATION_EXCEPTION", e.getMessage());
-                    } else if (e instanceof IgniteException) {
-                        errRes = new ErrorResult("APPLICATION_EXCEPTION", e.getMessage());
-                    } else {
-                        throw new CompletionException(e);
-                    }
-
-                    res.status(BAD_REQUEST);
-                    res.json(Map.of("error", errRes));
-
-                    return res;
-                });
-    }
-}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/AbstractConfigurationController.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/AbstractConfigurationController.java
new file mode 100644
index 000000000..fa86a3480
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/AbstractConfigurationController.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.rest.configuration;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.internal.configuration.rest.presentation.ConfigurationPresentation;
+import org.apache.ignite.internal.rest.configuration.exception.ConfigPathUnrecognizedException;
+import org.apache.ignite.internal.rest.configuration.exception.InvalidConfigFormatException;
+import org.apache.ignite.lang.IgniteException;
+
+/**
+ * Base configuration controller.
+ */
+public abstract class AbstractConfigurationController {
+    /** Presentation of the configuration. */
+    private final ConfigurationPresentation<String> cfgPresentation;
+
+    public AbstractConfigurationController(ConfigurationPresentation<String> cfgPresentation) {
+        this.cfgPresentation = cfgPresentation;
+    }
+
+    /**
+     * Returns configuration.
+     *
+     * @return the presentation of configuration.
+     */
+    public String getConfiguration() {
+        return this.cfgPresentation.represent();
+    }
+
+    /**
+     * Returns configuration represented by path.
+     *
+     * @param path to represent a configuration.
+     * @return system configuration represented by given path.
+     */
+    public String getConfigurationByPath(String path) {
+        try {
+            return cfgPresentation.representByPath(path);
+        } catch (IllegalArgumentException ex) {
+            throw new ConfigPathUnrecognizedException(ex);
+        }
+    }
+
+    /**
+     * Updates configuration.
+     *
+     * @param updatedConfiguration the configuration to update.
+     */
+    public CompletableFuture<Void> updateConfiguration(String updatedConfiguration) throws Throwable {
+        return cfgPresentation.update(updatedConfiguration)
+                .exceptionally(ex -> {
+                    if (ex instanceof CompletionException) {
+                        var cause = ex.getCause();
+                        if (cause instanceof IllegalArgumentException) {
+                            throw new InvalidConfigFormatException(ex);
+                        } else if (cause instanceof ConfigurationValidationException) {
+                            throw (ConfigurationValidationException) cause;
+                        }
+                    }
+                    throw new IgniteException(ex);
+                });
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/ClusterConfigurationController.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/ClusterConfigurationController.java
new file mode 100644
index 000000000..53586ba0f
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/ClusterConfigurationController.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.rest.configuration;
+
+import io.micronaut.http.MediaType;
+import io.micronaut.http.annotation.Body;
+import io.micronaut.http.annotation.Consumes;
+import io.micronaut.http.annotation.Controller;
+import io.micronaut.http.annotation.Get;
+import io.micronaut.http.annotation.Patch;
+import io.micronaut.http.annotation.PathVariable;
+import io.micronaut.http.annotation.Produces;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Named;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.internal.configuration.rest.presentation.ConfigurationPresentation;
+
+/**
+ * Cluster configuration controller.
+ */
+@Controller("/management/v1/configuration/cluster/")
+@ApiResponse(responseCode = "400", description = "Incorrect configuration")
+@ApiResponse(responseCode = "500", description = "Internal error")
+@Tag(name = "clusterConfiguration")
+public class ClusterConfigurationController extends AbstractConfigurationController {
+
+    public ClusterConfigurationController(@Named("clusterCfgPresentation") ConfigurationPresentation<String> clusterCfgPresentation) {
+        super(clusterCfgPresentation);
+    }
+
+    /**
+     * Returns cluster configuration in HOCON format. This is represented as a plain text.
+     *
+     * @return the whole cluster configuration in HOCON format.
+     */
+    @Operation(operationId = "getClusterConfiguration")
+    @ApiResponse(
+            responseCode = "200",
+            content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
+            description = "Get cluster configuration")
+    @Produces(MediaType.TEXT_PLAIN) // todo: IGNITE-17082
+    @Get
+    @Override
+    public String getConfiguration() {
+        return super.getConfiguration();
+    }
+
+    /**
+     * Returns configuration in HOCON format represented by path. This is represented as a plain text.
+     *
+     * @param path to represent a cluster configuration.
+     * @return system configuration in HOCON format represented by given path.
+     */
+    @Operation(operationId = "getClusterConfigurationByPath")
+    @ApiResponse(responseCode = "200",
+            content = @Content(mediaType = MediaType.TEXT_PLAIN,
+                    schema = @Schema(type = "string")),
+            description = "Configuration represented by path")
+    @Produces(MediaType.TEXT_PLAIN) // todo: IGNITE-17082
+    @Get("/{path}")
+    @Override
+    public String getConfigurationByPath(@PathVariable String path) {
+        return super.getConfigurationByPath(path);
+    }
+
+    /**
+     * Updates cluster configuration in HOCON format. This is represented as a plain text.
+     *
+     * @param updatedConfiguration the cluster configuration to update.
+     */
+    @Operation(operationId = "updateClusterConfiguration")
+    @ApiResponse(responseCode = "200", description = "Configuration updated")
+    @Consumes(MediaType.TEXT_PLAIN) // todo: IGNITE-17082
+    @Patch
+    @Override
+    public CompletableFuture<Void> updateConfiguration(@Body String updatedConfiguration) throws Throwable {
+        return super.updateConfiguration(updatedConfiguration);
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/NodeConfigurationController.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/NodeConfigurationController.java
new file mode 100644
index 000000000..9eafa2672
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/NodeConfigurationController.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.rest.configuration;
+
+import io.micronaut.http.MediaType;
+import io.micronaut.http.annotation.Body;
+import io.micronaut.http.annotation.Consumes;
+import io.micronaut.http.annotation.Controller;
+import io.micronaut.http.annotation.Get;
+import io.micronaut.http.annotation.Patch;
+import io.micronaut.http.annotation.PathVariable;
+import io.micronaut.http.annotation.Produces;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Named;
+import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.internal.configuration.rest.presentation.ConfigurationPresentation;
+
+/**
+ * Node configuration controller.
+ */
+@Controller("/management/v1/configuration/node")
+@ApiResponse(responseCode = "400", description = "Incorrect configuration")
+@ApiResponse(responseCode = "500", description = "Internal error")
+@Tag(name = "nodeConfiguration")
+public class NodeConfigurationController extends AbstractConfigurationController {
+
+    public NodeConfigurationController(@Named("nodeCfgPresentation") ConfigurationPresentation<String> nodeCfgPresentation) {
+        super(nodeCfgPresentation);
+    }
+
+    /**
+     * Returns node configuration in HOCON format. This is represented as a plain text.
+     *
+     * @return the whole node configuration in HOCON format.
+     */
+    @Operation(operationId = "getNodeConfiguration")
+    @ApiResponse(responseCode = "200",
+            content = @Content(mediaType = MediaType.TEXT_PLAIN,
+                    schema = @Schema(type = "string")),
+            description = "Whole node configuration")
+    @Produces(MediaType.TEXT_PLAIN) // todo: IGNITE-17082
+    @Get
+    @Override
+    public String getConfiguration() {
+        return super.getConfiguration();
+    }
+
+    /**
+     * Returns configuration in HOCON format represented by path. This is represented as a plain text.
+     *
+     * @param path to represent a node configuration.
+     * @return system configuration in HOCON format represented by given path.
+     */
+    @Operation(operationId = "getNodeConfigurationByPath")
+    @ApiResponse(responseCode = "200",
+            content = @Content(mediaType = MediaType.TEXT_PLAIN,
+                    schema = @Schema(type = "string")),
+            description = "Configuration represented by path")
+    @Produces(MediaType.TEXT_PLAIN) // todo: IGNITE-17082
+    @Get("/{path}")
+    @Override
+    public String getConfigurationByPath(@PathVariable String path) {
+        return super.getConfigurationByPath(path);
+    }
+
+    /**
+     * Updates node configuration in HOCON format. This is represented as a plain text.
+     *
+     * @param updatedConfiguration the node configuration to update. This is represented as a plain text.
+     */
+    @Operation(operationId = "updateNodeConfiguration")
+    @ApiResponse(responseCode = "200", description = "Configuration updated")
+    @Consumes(MediaType.TEXT_PLAIN) // todo: IGNITE-17082
+    @Patch
+    @Override
+    public CompletableFuture<Void> updateConfiguration(@Body String updatedConfiguration) throws Throwable {
+        return super.updateConfiguration(updatedConfiguration);
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/PresentationsFactory.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/PresentationsFactory.java
new file mode 100644
index 000000000..399f74d15
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/PresentationsFactory.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.rest.configuration;
+
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.context.annotation.Factory;
+import jakarta.inject.Named;
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.configuration.ConfigurationManager;
+import org.apache.ignite.internal.configuration.rest.presentation.ConfigurationPresentation;
+import org.apache.ignite.internal.configuration.rest.presentation.hocon.HoconPresentation;
+import org.apache.ignite.internal.rest.api.RestFactory;
+
+/**
+ * Factory that defines beans required for the rest module.
+ */
+@Factory
+public class PresentationsFactory implements RestFactory {
+    private final ConfigurationPresentation<String> nodeCfgPresentation;
+    private final ConfigurationPresentation<String> clusterCfgPresentation;
+
+    public PresentationsFactory(ConfigurationManager nodeCfgMgr, ConfigurationManager clusterCfgMgr) {
+        this.nodeCfgPresentation = new HoconPresentation(nodeCfgMgr.configurationRegistry());
+        this.clusterCfgPresentation = new HoconPresentation(clusterCfgMgr.configurationRegistry());
+    }
+
+    @Bean
+    @Singleton
+    @Named("clusterCfgPresentation")
+    public ConfigurationPresentation<String> clusterCfgPresentation() {
+        return clusterCfgPresentation;
+    }
+
+    @Bean
+    @Singleton
+    @Named("nodeCfgPresentation")
+    public ConfigurationPresentation<String> nodeCfgPresentation() {
+        return nodeCfgPresentation;
+    }
+}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestHandlersRegister.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/ConfigPathUnrecognizedException.java
similarity index 70%
rename from modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestHandlersRegister.java
rename to modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/ConfigPathUnrecognizedException.java
index c55e7d6a7..dc05c79fe 100644
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestHandlersRegister.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/ConfigPathUnrecognizedException.java
@@ -15,18 +15,13 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.rest.api;
-
-import java.util.function.Consumer;
+package org.apache.ignite.internal.rest.configuration.exception;
 
 /**
- * Register for REST handlers.
+ * Exception that is thrown when the wrong configuration path is given.
  */
-public interface RestHandlersRegister {
-    /**
-     * Registers handlers on the provided {@link Routes}.
-     *
-     * @param registerAction registration action
-     */
-    void registerHandlers(Consumer<Routes> registerAction);
+public class ConfigPathUnrecognizedException extends RuntimeException {
+    public ConfigPathUnrecognizedException(Throwable cause) {
+        super(cause);
+    }
 }
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/InvalidConfigFormatException.java
old mode 100755
new mode 100644
similarity index 73%
copy from modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
copy to modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/InvalidConfigFormatException.java
index 81b40c399..7c754286e
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/InvalidConfigFormatException.java
@@ -15,8 +15,13 @@
  * limitations under the License.
  */
 
+package org.apache.ignite.internal.rest.configuration.exception;
+
 /**
- * This package contains an implementation of Ignite component related to REST interface.
+ * Exception that is thrown when the wrong configuration is given.
  */
-
-package org.apache.ignite.internal.rest;
+public class InvalidConfigFormatException extends RuntimeException {
+    public InvalidConfigFormatException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/ConfigPathUnrecognizedExceptionHandler.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/ConfigPathUnrecognizedExceptionHandler.java
new file mode 100644
index 000000000..f15cba486
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/ConfigPathUnrecognizedExceptionHandler.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.internal.rest.configuration.exception.handler;
+
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.MediaType;
+import io.micronaut.http.annotation.Produces;
+import io.micronaut.http.server.exceptions.ExceptionHandler;
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.rest.api.ErrorResult;
+import org.apache.ignite.internal.rest.configuration.exception.ConfigPathUnrecognizedException;
+
+/**
+ * Handles {@link ConfigPathUnrecognizedException} and represents it as a rest response.
+ */
+@Produces(MediaType.APPLICATION_JSON)
+@Singleton
+@Requires(classes = {ConfigPathUnrecognizedException.class, ExceptionHandler.class})
+public class ConfigPathUnrecognizedExceptionHandler implements
+        ExceptionHandler<ConfigPathUnrecognizedException, HttpResponse<ErrorResult>> {
+
+    @Override
+    public HttpResponse<ErrorResult> handle(HttpRequest request, ConfigPathUnrecognizedException exception) {
+        ErrorResult errorResult = new ErrorResult("CONFIG_PATH_UNRECOGNIZED", exception.getCause().getMessage());
+        return HttpResponse.badRequest().body(errorResult);
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/ConfigurationValidationExceptionHandler.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/ConfigurationValidationExceptionHandler.java
new file mode 100644
index 000000000..d9fe8cb98
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/ConfigurationValidationExceptionHandler.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.rest.configuration.exception.handler;
+
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.MediaType;
+import io.micronaut.http.annotation.Produces;
+import io.micronaut.http.server.exceptions.ExceptionHandler;
+import jakarta.inject.Singleton;
+import java.util.stream.Collectors;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.configuration.validation.ValidationIssue;
+import org.apache.ignite.internal.rest.api.ErrorResult;
+
+/**
+ * Handles {@link ConfigurationValidationException} and represents it as a rest response.
+ */
+@Produces(MediaType.APPLICATION_JSON)
+@Singleton
+@Requires(classes = {ConfigurationValidationException.class, ExceptionHandler.class})
+public class ConfigurationValidationExceptionHandler implements
+        ExceptionHandler<ConfigurationValidationException, HttpResponse<ErrorResult>> {
+
+    @Override
+    public HttpResponse<ErrorResult> handle(HttpRequest request, ConfigurationValidationException exception) {
+        String joinedMessage = exception.getIssues()
+                .stream()
+                .map(ValidationIssue::message)
+                .collect(Collectors.joining(","));
+        ErrorResult errorResult = new ErrorResult("VALIDATION_EXCEPTION", joinedMessage);
+        return HttpResponse.badRequest().body(errorResult);
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/IgniteExceptionHandler.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/IgniteExceptionHandler.java
new file mode 100644
index 000000000..df2f4704a
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/IgniteExceptionHandler.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.rest.configuration.exception.handler;
+
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.annotation.Produces;
+import io.micronaut.http.server.exceptions.ExceptionHandler;
+import jakarta.inject.Singleton;
+import org.apache.ignite.configuration.validation.ConfigurationValidationException;
+import org.apache.ignite.internal.rest.api.ErrorResult;
+import org.apache.ignite.lang.IgniteException;
+
+/**
+ * Handles {@link ConfigurationValidationException} and represents it as a rest response.
+ */
+@Produces
+@Singleton
+@Requires(classes = {IgniteException.class, ExceptionHandler.class})
+public class IgniteExceptionHandler implements ExceptionHandler<IgniteException, HttpResponse<ErrorResult>> {
+
+    @Override
+    public HttpResponse<ErrorResult> handle(HttpRequest request, IgniteException exception) {
+        ErrorResult errorResult = new ErrorResult("APPLICATION_EXCEPTION", exception.getMessage());
+        return HttpResponse.serverError().body(errorResult);
+    }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/InvalidConfigFormatExceptionHandler.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/InvalidConfigFormatExceptionHandler.java
new file mode 100644
index 000000000..cc71e1a4c
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/InvalidConfigFormatExceptionHandler.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.rest.configuration.exception.handler;
+
+import io.micronaut.context.annotation.Requires;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.MediaType;
+import io.micronaut.http.annotation.Produces;
+import io.micronaut.http.server.exceptions.ExceptionHandler;
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.rest.api.ErrorResult;
+import org.apache.ignite.internal.rest.configuration.exception.InvalidConfigFormatException;
+
+/**
+ * Handles {@link InvalidConfigFormatException} and represents it as a rest response.
+ */
+@Produces(MediaType.APPLICATION_JSON)
+@Singleton
+@Requires(classes = {InvalidConfigFormatException.class, ExceptionHandler.class})
+public class InvalidConfigFormatExceptionHandler implements ExceptionHandler<InvalidConfigFormatException, HttpResponse<ErrorResult>> {
+
+    @Override
+    public HttpResponse<ErrorResult> handle(HttpRequest request, InvalidConfigFormatException exception) {
+        ErrorResult errorResult = new ErrorResult("INVALID_CONFIG_FORMAT", exception.getCause().getCause().getMessage());
+        return HttpResponse.badRequest().body(errorResult);
+    }
+}
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/package-info.java
old mode 100755
new mode 100644
similarity index 78%
copy from modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
copy to modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/package-info.java
index 81b40c399..80af2d764
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/handler/package-info.java
@@ -16,7 +16,8 @@
  */
 
 /**
- * This package contains an implementation of Ignite component related to REST interface.
+ * This package contains classes that implement a number of ExceptionHandlers. They wrap plain exceptions into human-readable rest
+ * responses.
  */
 
-package org.apache.ignite.internal.rest;
+package org.apache.ignite.internal.rest.configuration.exception.handler;
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/package-info.java
old mode 100755
new mode 100644
similarity index 83%
copy from modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
copy to modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/package-info.java
index 81b40c399..33aa0d5ae
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/rest/configuration/exception/package-info.java
@@ -16,7 +16,7 @@
  */
 
 /**
- * This package contains an implementation of Ignite component related to REST interface.
+ * This package contains classes that represent exceptions that can be thrown and their handlers.
  */
 
-package org.apache.ignite.internal.rest;
+package org.apache.ignite.internal.rest.configuration.exception;
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/ClusterConfigurationControllerTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/ClusterConfigurationControllerTest.java
new file mode 100644
index 000000000..98100e327
--- /dev/null
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/ClusterConfigurationControllerTest.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.rest.configuration;
+
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.context.annotation.Replaces;
+import io.micronaut.http.client.HttpClient;
+import io.micronaut.http.client.annotation.Client;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
+import org.apache.ignite.internal.configuration.ConfigurationRegistry;
+import org.apache.ignite.internal.configuration.rest.presentation.ConfigurationPresentation;
+import org.apache.ignite.internal.configuration.rest.presentation.hocon.HoconPresentation;
+
+/**
+ * Functional test for {@link ClusterConfigurationController}.
+ */
+@MicronautTest
+class ClusterConfigurationControllerTest extends ConfigurationControllerBaseTest {
+
+    @Inject
+    @Client("/management/v1/configuration/cluster/")
+    HttpClient client;
+
+    @Override
+    HttpClient client() {
+        return client;
+    }
+
+    /**
+     * Creates test hocon configuration representation.
+     */
+    @Bean
+    @Named("clusterCfgPresentation")
+    @Replaces(factory = PresentationsFactory.class)
+    public ConfigurationPresentation<String> cfgPresentation(ConfigurationRegistry configurationRegistry) {
+        return new HoconPresentation(configurationRegistry);
+    }
+}
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/ConfigurationControllerBaseTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/ConfigurationControllerBaseTest.java
new file mode 100644
index 000000000..dd2864882
--- /dev/null
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/ConfigurationControllerBaseTest.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.rest.configuration;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import io.micronaut.context.ApplicationContext;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpStatus;
+import io.micronaut.http.MediaType;
+import io.micronaut.http.client.HttpClient;
+import io.micronaut.http.client.exceptions.HttpClientResponseException;
+import io.micronaut.runtime.server.EmbeddedServer;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
+import org.apache.ignite.internal.configuration.ConfigurationRegistry;
+import org.apache.ignite.internal.configuration.rest.presentation.ConfigurationPresentation;
+import org.apache.ignite.internal.configuration.rest.presentation.TestRootConfiguration;
+import org.apache.ignite.internal.rest.api.ErrorResult;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The base test for configuration controllers.
+ */
+@MicronautTest
+public abstract class ConfigurationControllerBaseTest {
+    @Inject
+    private EmbeddedServer server;
+
+    @Inject
+    private ConfigurationPresentation<String> cfgPresentation;
+
+    @Inject
+    private ConfigurationRegistry configurationRegistry;
+
+    @Inject
+    private ApplicationContext context;
+
+    abstract HttpClient client();
+
+    @BeforeEach
+    void beforeEach() throws Exception {
+        var cfg = configurationRegistry.getConfiguration(TestRootConfiguration.KEY);
+        cfg.change(c -> c.changeFoo("foo").changeSubCfg(subCfg -> subCfg.changeBar("bar"))).get(1, SECONDS);
+    }
+
+    @Test
+    void testGetConfig() {
+        var response = client().toBlocking().exchange("", String.class);
+
+        assertEquals(HttpStatus.OK, response.status());
+        assertEquals(cfgPresentation.represent(), response.body());
+    }
+
+    @Test
+    void testGetConfigByPath() {
+        var response = client().toBlocking().exchange("/root.subCfg", String.class);
+
+        assertEquals(HttpStatus.OK, response.status());
+        assertEquals(cfgPresentation.representByPath("root.subCfg"), response.body());
+    }
+
+    @Test
+    void testUpdateConfig() {
+        String givenChangedConfig = "{root:{foo:foo,subCfg:{bar:changed}}}";
+
+        var response = client().toBlocking().exchange(
+                HttpRequest.PATCH("", givenChangedConfig).contentType(MediaType.TEXT_PLAIN)
+        );
+        assertEquals(response.status(), HttpStatus.OK);
+
+        String changedConfigValue = client().toBlocking().exchange("/root.subCfg.bar", String.class).body();
+        assertEquals("\"changed\"", changedConfigValue);
+    }
+
+    @Test
+    void testUnrecognizedConfigPath() {
+        var thrown = assertThrows(
+                HttpClientResponseException.class,
+                () -> client().toBlocking().exchange("/no-such-root.some-value")
+        );
+
+        assertEquals(HttpStatus.BAD_REQUEST, thrown.getResponse().status());
+
+        var errorResult = getErrorResult(thrown);
+        assertEquals("CONFIG_PATH_UNRECOGNIZED", errorResult.type());
+        assertEquals("Configuration value 'no-such-root' has not been found", errorResult.message());
+    }
+
+    @Test
+    void testUnrecognizedConfigPathForUpdate() {
+        String givenBrokenConfig = "{root:{foo:foo,subCfg:{no-such-bar:bar}}}";
+
+        var thrown = assertThrows(
+                HttpClientResponseException.class,
+                () -> client().toBlocking().exchange(HttpRequest.PATCH("", givenBrokenConfig).contentType(MediaType.TEXT_PLAIN))
+        );
+
+        assertEquals(HttpStatus.BAD_REQUEST, thrown.getResponse().status());
+
+        var errorResult = getErrorResult(thrown);
+        assertEquals("INVALID_CONFIG_FORMAT", errorResult.type());
+        assertEquals("'root.subCfg' configuration doesn't have the 'no-such-bar' sub-configuration", errorResult.message());
+    }
+
+    @Test
+    void testValidationForUpdate() {
+        String givenConfigWithError = "{root:{foo:error,subCfg:{bar:bar}}}";
+
+        var thrown = assertThrows(
+                HttpClientResponseException.class,
+                () -> client().toBlocking().exchange(HttpRequest.PATCH("", givenConfigWithError).contentType(MediaType.TEXT_PLAIN))
+        );
+
+        assertEquals(HttpStatus.BAD_REQUEST, thrown.getResponse().status());
+
+        var errorResult = getErrorResult(thrown);
+        assertEquals("VALIDATION_EXCEPTION", errorResult.type());
+        assertEquals("Error word", errorResult.message());
+    }
+
+    @NotNull
+    private ErrorResult getErrorResult(HttpClientResponseException exception) {
+        return exception.getResponse().getBody(ErrorResult.class).orElseThrow();
+    }
+}
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/NodeConfigurationControllerTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/NodeConfigurationControllerTest.java
new file mode 100644
index 000000000..679183995
--- /dev/null
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/NodeConfigurationControllerTest.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.rest.configuration;
+
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.context.annotation.Replaces;
+import io.micronaut.http.client.HttpClient;
+import io.micronaut.http.client.annotation.Client;
+import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
+import org.apache.ignite.internal.configuration.ConfigurationRegistry;
+import org.apache.ignite.internal.configuration.rest.presentation.ConfigurationPresentation;
+import org.apache.ignite.internal.configuration.rest.presentation.hocon.HoconPresentation;
+
+/**
+ * Functional test for {@link NodeConfigurationController}.
+ */
+@MicronautTest
+class NodeConfigurationControllerTest extends ConfigurationControllerBaseTest {
+
+    @Inject
+    @Client("/management/v1/configuration/node/")
+    HttpClient client;
+
+    @Override
+    HttpClient client() {
+        return client;
+    }
+
+    /**
+     * Creates test hocon configuration representation.
+     */
+    @Bean
+    @Named("nodeCfgPresentation")
+    @Replaces(factory = PresentationsFactory.class)
+    public ConfigurationPresentation<String> cfgPresentation(ConfigurationRegistry configurationRegistry) {
+        return new HoconPresentation(configurationRegistry);
+    }
+}
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestFactory.java b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestFactory.java
new file mode 100644
index 000000000..2c581753d
--- /dev/null
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestFactory.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.rest.configuration;
+
+import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
+
+import io.micronaut.context.annotation.Bean;
+import io.micronaut.context.annotation.Factory;
+import jakarta.inject.Singleton;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.validation.ValidationContext;
+import org.apache.ignite.configuration.validation.ValidationIssue;
+import org.apache.ignite.configuration.validation.Validator;
+import org.apache.ignite.internal.configuration.ConfigurationRegistry;
+import org.apache.ignite.internal.configuration.rest.presentation.TestRootConfiguration;
+import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
+
+/**
+ * Factory that creates beans needed for unit tests.
+ */
+@Factory
+public class TestFactory {
+    /**
+     * Creates test configuration registry.
+     */
+    @Singleton
+    @Bean(preDestroy = "stop")
+    public ConfigurationRegistry configurationRegistry() {
+        Validator<Value, Object> validator = new Validator<>() {
+            /** {@inheritDoc} */
+            @Override
+            public void validate(Value annotation, ValidationContext<Object> ctx) {
+                if (Objects.equals("error", ctx.getNewValue())) {
+                    ctx.addIssue(new ValidationIssue("Error word"));
+                }
+            }
+        };
+
+        var configurationRegistry = new ConfigurationRegistry(
+                List.of(TestRootConfiguration.KEY),
+                Map.of(Value.class, Set.of(validator)),
+                new TestConfigurationStorage(LOCAL),
+                List.of(),
+                List.of()
+        );
+
+        configurationRegistry.start();
+
+        return configurationRegistry;
+    }
+}
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/Router.java b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestRootConfigurationSchema.java
similarity index 60%
rename from modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/Router.java
rename to modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestRootConfigurationSchema.java
index 6f3f4af42..fc180b8b7 100644
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/Router.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestRootConfigurationSchema.java
@@ -15,21 +15,22 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.rest.routes;
+package org.apache.ignite.internal.rest.configuration;
 
-import io.netty.handler.codec.http.HttpRequest;
-import java.util.Optional;
-import org.apache.ignite.internal.rest.api.Route;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.Value;
 
 /**
- * Dispatcher of http requests.
+ * Test root configuration schema.
  */
-public interface Router {
-    /**
-     * Finds the route by request.
-     *
-     * @param req Request.
-     * @return Route if found.
-     */
-    Optional<Route> route(HttpRequest req);
+@ConfigurationRoot(rootName = "root")
+public class TestRootConfigurationSchema {
+    /** Foo field. */
+    @Value(hasDefault = true)
+    public String foo = "foo";
+
+    /** Sub configuration schema. */
+    @ConfigValue
+    public TestSubConfigurationSchema subCfg;
 }
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestSubConfigurationSchema.java
old mode 100755
new mode 100644
similarity index 70%
copy from modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
copy to modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestSubConfigurationSchema.java
index 81b40c399..28bff35c1
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/rest/configuration/TestSubConfigurationSchema.java
@@ -15,8 +15,17 @@
  * limitations under the License.
  */
 
+package org.apache.ignite.internal.rest.configuration;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.Value;
+
 /**
- * This package contains an implementation of Ignite component related to REST interface.
+ * Test sub configuration schema.
  */
-
-package org.apache.ignite.internal.rest;
+@Config
+public class TestSubConfigurationSchema {
+    /** Bar field. */
+    @Value(hasDefault = true)
+    public String bar = "bar";
+}
diff --git a/modules/rest-api/pom.xml b/modules/rest-api/pom.xml
index 08a442150..6cf400020 100644
--- a/modules/rest-api/pom.xml
+++ b/modules/rest-api/pom.xml
@@ -33,46 +33,29 @@
     <version>3.0.0-SNAPSHOT</version>
 
     <dependencies>
-        <!-- 3rd party dependencies -->
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
-            <artifactId>jackson-databind</artifactId>
+            <artifactId>jackson-annotations</artifactId>
         </dependency>
 
         <dependency>
-            <groupId>io.netty</groupId>
-            <artifactId>netty-codec-http</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>org.jetbrains</groupId>
-            <artifactId>annotations</artifactId>
-        </dependency>
-
-        <!-- Test dependencies -->
-        <dependency>
-            <groupId>org.hamcrest</groupId>
-            <artifactId>hamcrest</artifactId>
-            <scope>test</scope>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
         </dependency>
 
         <dependency>
-            <groupId>org.junit.jupiter</groupId>
-            <artifactId>junit-jupiter-api</artifactId>
-            <scope>test</scope>
+            <groupId>jakarta.annotation</groupId>
+            <artifactId>jakarta.annotation-api</artifactId>
         </dependency>
 
         <dependency>
-            <groupId>org.junit.jupiter</groupId>
-            <artifactId>junit-jupiter-engine</artifactId>
-            <scope>test</scope>
+            <groupId>jakarta.inject</groupId>
+            <artifactId>jakarta.inject-api</artifactId>
         </dependency>
 
-        <!-- Logging in tests -->
         <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-jdk14</artifactId>
-            <scope>test</scope>
+            <groupId>io.swagger.core.v3</groupId>
+            <artifactId>swagger-annotations</artifactId>
         </dependency>
     </dependencies>
 
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/ErrorResult.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/ErrorResult.java
index 41b400adb..8dd6e570b 100644
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/ErrorResult.java
+++ b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/ErrorResult.java
@@ -17,6 +17,10 @@
 
 package org.apache.ignite.internal.rest.api;
 
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonGetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
 /**
  * Error result represent a tuple of error type and user-friendly error message.
  */
@@ -33,7 +37,8 @@ public class ErrorResult {
      * @param type    Error type describing the class of the error occurred.
      * @param message User-friendly error message.
      */
-    public ErrorResult(String type, String message) {
+    @JsonCreator
+    public ErrorResult(@JsonProperty("type") String type, @JsonProperty("message") String message) {
         this.type = type;
         this.message = message;
     }
@@ -43,6 +48,7 @@ public class ErrorResult {
      *
      * @return Error type describing the class of the error occurred.
      */
+    @JsonGetter("type")
     public String type() {
         return type;
     }
@@ -52,6 +58,7 @@ public class ErrorResult {
      *
      * @return User-friendly error message.
      */
+    @JsonGetter("message")
     public String message() {
         return message;
     }
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RequestHandler.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RequestHandler.java
deleted file mode 100644
index 22d0d9441..000000000
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RequestHandler.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.rest.api;
-
-import java.util.concurrent.CompletableFuture;
-
-/**
- * Interface for defining REST API request handlers.
- */
-public interface RequestHandler {
-    /**
-     * Handles the given request and writes the response into the provided {@link RestApiHttpResponse}.
-     *
-     * @param request REST API request
-     * @param response REST API response
-     * @return Future that resolves as soon as the response is ready to be sent to the caller. It may return either a new response object
-     *         or the same as the provided via the {@code response} parameter.
-     */
-    CompletableFuture<RestApiHttpResponse> handle(RestApiHttpRequest request, RestApiHttpResponse response);
-}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestApiHttpRequest.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestApiHttpRequest.java
deleted file mode 100644
index af492526f..000000000
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestApiHttpRequest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.rest.api;
-
-import io.netty.handler.codec.http.FullHttpRequest;
-import java.util.Collections;
-import java.util.Map;
-
-/**
- * HTTP request wrapper with GET query params if exists.
- */
-public class RestApiHttpRequest {
-    /** Request. */
-    private final FullHttpRequest req;
-
-    /** Query params. */
-    private final Map<String, String> qryParams;
-
-    /**
-     * Creates a new instance of http request.
-     *
-     * @param req       Request.
-     * @param qryParams Query params.
-     */
-    public RestApiHttpRequest(FullHttpRequest req, Map<String, String> qryParams) {
-        this.req = req;
-        this.qryParams = Collections.unmodifiableMap(qryParams);
-    }
-
-    /**
-     * Returns complete HTTP request.
-     *
-     * @return Complete HTTP request.
-     */
-    public FullHttpRequest request() {
-        return req;
-    }
-
-    /**
-     * Returns query parameters associated with the request.
-     *
-     * @return Query parameters.
-     */
-    public Map<String, String> queryParams() {
-        return qryParams;
-    }
-}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestApiHttpResponse.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestApiHttpResponse.java
deleted file mode 100644
index daa8ead5b..000000000
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestApiHttpResponse.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.rest.api;
-
-import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpHeaderValues;
-import io.netty.handler.codec.http.HttpHeaders;
-import io.netty.handler.codec.http.HttpResponse;
-import io.netty.handler.codec.http.HttpResponseStatus;
-import io.netty.handler.codec.http.HttpVersion;
-
-/**
- * Simple wrapper of HTTP response with some helper methods for filling it with headers and content.
- */
-public class RestApiHttpResponse {
-    private static final ObjectMapper objectMapper = new ObjectMapper()
-            .setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
-
-    /** Response. */
-    private final HttpResponse res;
-
-    /** Content. */
-    private byte[] content;
-
-    /**
-     * Creates a new HTTP response with the given message body.
-     *
-     * @param res     Response.
-     * @param content Content.
-     */
-    public RestApiHttpResponse(HttpResponse res, byte[] content) {
-        this.res = res;
-        this.content = content;
-    }
-
-    /**
-     * Creates a new HTTP response with the given headers and status.
-     *
-     * @param res Response.
-     */
-    public RestApiHttpResponse(HttpResponse res) {
-        this.res = res;
-    }
-
-    /**
-     * Set raw bytes as response body.
-     *
-     * @param content Content data.
-     * @return Updated response.
-     */
-    public RestApiHttpResponse content(byte[] content) {
-        this.content = content;
-        return this;
-    }
-
-    /**
-     * Returns content in a form of raw bytes.
-     *
-     * @return Content.
-     */
-    public byte[] content() {
-        return content;
-    }
-
-    /**
-     * Set JSON representation of input object as response body.
-     *
-     * @param content Content object.
-     * @return Updated response.
-     */
-    public RestApiHttpResponse json(Object content) {
-        try {
-            this.content = objectMapper.writeValueAsBytes(content);
-        } catch (JsonProcessingException e) {
-            throw new IllegalArgumentException("Unable to serialize JSON content", e);
-        }
-
-        headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON.toString());
-
-        return this;
-    }
-
-    /**
-     * Returns HTTP status of this response.
-     *
-     * @return HTTP Status.
-     */
-    public HttpResponseStatus status() {
-        return res.status();
-    }
-
-    /**
-     * Sets HTTP status.
-     *
-     * @param status Status.
-     * @return Updated response.
-     */
-    public RestApiHttpResponse status(HttpResponseStatus status) {
-        res.setStatus(status);
-        return this;
-    }
-
-    /**
-     * Returns protocol version.
-     *
-     * @return Protocol version
-     */
-    public HttpVersion protocolVersion() {
-        return res.protocolVersion();
-    }
-
-    /**
-     * Sets protocol version.
-     *
-     * @param httpVer HTTP version.
-     * @return Updated response.
-     */
-    public RestApiHttpResponse protocolVersion(HttpVersion httpVer) {
-        res.setProtocolVersion(httpVer);
-        return this;
-    }
-
-    /**
-     * Returns mutable response headers.
-     *
-     * @return Mutable response headers.
-     */
-    public HttpHeaders headers() {
-        return res.headers();
-    }
-}
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/package-info.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestFactory.java
old mode 100755
new mode 100644
similarity index 83%
rename from modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/package-info.java
rename to modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestFactory.java
index c7ab2540c..e1e89c775
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/package-info.java
+++ b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/RestFactory.java
@@ -15,8 +15,10 @@
  * limitations under the License.
  */
 
+package org.apache.ignite.internal.rest.api;
+
 /**
- * This package contains classes that represent inbound requests, outbound responses and main REST handler.
+ * Factory that produces all beans that is necessary for the controller class.
  */
-
-package org.apache.ignite.internal.rest.netty;
+public interface RestFactory {
+}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Route.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Route.java
deleted file mode 100644
index 90c3b474d..000000000
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Route.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.rest.api;
-
-import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpMethod;
-import io.netty.handler.codec.http.HttpRequest;
-import java.util.ArrayDeque;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * URI route with appropriate handler for request.
- */
-public class Route {
-    /** Route. */
-    private final String route;
-
-    /** Method. */
-    private final HttpMethod method;
-
-    /** Accept type. */
-    @Nullable
-    private final String acceptType;
-
-    /** Handler. */
-    private final RequestHandler hnd;
-
-    /**
-     * Create a new URI route with the given parameters.
-     *
-     * @param route      Route.
-     * @param method     Method.
-     * @param acceptType Accept type.
-     * @param hnd        Request handler.
-     */
-    public Route(
-            String route,
-            HttpMethod method,
-            @Nullable String acceptType,
-            RequestHandler hnd
-    ) {
-        this.route = route;
-        this.method = method;
-        this.acceptType = acceptType;
-        this.hnd = hnd;
-    }
-
-    /**
-     * Handles the query by populating the received response object.
-     *
-     * @param req  Request.
-     * @param resp Response.
-     */
-    public CompletableFuture<RestApiHttpResponse> handle(FullHttpRequest req, RestApiHttpResponse resp) {
-        return hnd.handle(new RestApiHttpRequest(req, paramsDecode(req.uri())), resp);
-    }
-
-    /**
-     * Checks if the current route matches the request.
-     *
-     * @param req Request.
-     * @return true if route matches the request, else otherwise.
-     */
-    public boolean match(HttpRequest req) {
-        return req.method().equals(method)
-                && matchUri(req.uri())
-                && matchContentType(req.headers().get(HttpHeaderNames.CONTENT_TYPE));
-    }
-
-    /**
-     * Returns {@code true} if route matches the request, else otherwise.
-     *
-     * @param s Content type.
-     * @return {@code true} if route matches the request, else otherwise.
-     */
-    private boolean matchContentType(String s) {
-        return (acceptType == null) || (acceptType.equals(s));
-    }
-
-    /**
-     * Checks the current route matches input uri. REST API like URIs "/user/:user" is also supported.
-     *
-     * @param uri Input URI
-     * @return true if route matches the request, else otherwise.
-     */
-    private boolean matchUri(String uri) {
-        var receivedParts = new ArrayDeque<>(Arrays.asList(uri.split("/")));
-        var realParts = new ArrayDeque<>(Arrays.asList(route.split("/")));
-
-        String part;
-        while ((part = realParts.pollFirst()) != null) {
-            String receivedPart = receivedParts.pollFirst();
-            if (receivedPart == null) {
-                return false;
-            }
-
-            if (part.startsWith(":")) {
-                continue;
-            }
-
-            if (!part.equals(receivedPart)) {
-                return false;
-            }
-        }
-
-        return receivedParts.isEmpty();
-    }
-
-    /**
-     * Decodes params from REST like URIs "/user/:user".
-     *
-     * @param uri Input URI.
-     * @return Map of decoded params.
-     * @throws IllegalArgumentException if provided URI is incorrect.
-     */
-    private Map<String, String> paramsDecode(String uri) {
-        var receivedParts = new ArrayDeque<>(Arrays.asList(uri.split("/")));
-        var realParts = new ArrayDeque<>(Arrays.asList(route.split("/")));
-
-        Map<String, String> res = new HashMap<>();
-
-        String part;
-        while ((part = realParts.pollFirst()) != null) {
-            String receivedPart = receivedParts.pollFirst();
-            if (receivedPart == null) {
-                throw new IllegalArgumentException("URI is incorrect");
-            }
-
-            if (part.startsWith(":")) {
-                res.put(part.substring(1), receivedPart);
-                continue;
-            }
-
-            if (!part.equals(receivedPart)) {
-                throw new IllegalArgumentException("URI is incorrect");
-            }
-        }
-
-        return res;
-    }
-}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Routes.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Routes.java
deleted file mode 100644
index 2dc53f33d..000000000
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Routes.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.rest.api;
-
-import io.netty.handler.codec.http.HttpMethod;
-
-/**
- * Allows to configure routes to REST handlers mapping.
- */
-public interface Routes {
-    /**
-     * Adds the route to the router chain.
-     *
-     * @param route Route
-     */
-    void addRoute(Route route);
-
-    /**
-     * Adds a GET handler.
-     *
-     * @param route      Route.
-     * @param acceptType Accept type.
-     * @param hnd        Actual handler of the request.
-     * @return Router
-     */
-    default Routes get(String route, String acceptType, RequestHandler hnd) {
-        addRoute(new Route(route, HttpMethod.GET, acceptType, hnd));
-        return this;
-    }
-
-    /**
-     * Adds a GET handler.
-     *
-     * @param route Route.
-     * @param hnd   Actual handler of the request.
-     * @return Router
-     */
-    default Routes get(String route, RequestHandler hnd) {
-        addRoute(new Route(route, HttpMethod.GET, null, hnd));
-        return this;
-    }
-
-    /**
-     * Adds a PUT handler.
-     *
-     * @param route      Route.
-     * @param acceptType Accept type.
-     * @param hnd        Actual handler of the request.
-     * @return Router
-     */
-    default Routes put(String route, String acceptType, RequestHandler hnd) {
-        addRoute(new Route(route, HttpMethod.PUT, acceptType, hnd));
-        return this;
-    }
-
-    /**
-     * Adds a PATCH handler.
-     *
-     * @param route      Route.
-     * @param acceptType Accept type.
-     * @param hnd        Actual handler of the request.
-     * @return Router
-     */
-    default Routes patch(String route, String acceptType, RequestHandler hnd) {
-        addRoute(new Route(route, HttpMethod.PATCH, acceptType, hnd));
-        return this;
-    }
-
-    /**
-     * Adds a POST handler.
-     *
-     * @param route      Route.
-     * @param acceptType Accept type.
-     * @param hnd        Actual handler of the request.
-     * @return Router
-     */
-    default Routes post(String route, String acceptType, RequestHandler hnd) {
-        addRoute(new Route(route, HttpMethod.POST, acceptType, hnd));
-        return this;
-    }
-}
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/package-info.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/package-info.java
old mode 100755
new mode 100644
similarity index 86%
rename from modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/package-info.java
rename to modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/package-info.java
index abad541e4..678a10d96
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/package-info.java
+++ b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/package-info.java
@@ -16,7 +16,7 @@
  */
 
 /**
- * This package contains classes that represent URI routers and their dispatching.
+ * This package contains common DTOs and interfaces for REST API.
  */
 
-package org.apache.ignite.internal.rest.routes;
+package org.apache.ignite.internal.rest.api;
diff --git a/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/api/RestApiHttpResponseTest.java b/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/api/RestApiHttpResponseTest.java
deleted file mode 100644
index 241909940..000000000
--- a/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/api/RestApiHttpResponseTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.rest.api;
-
-import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
-import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON;
-import static io.netty.handler.codec.http.HttpResponseStatus.OK;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import io.netty.handler.codec.http.DefaultHttpResponse;
-import io.netty.handler.codec.http.HttpVersion;
-import java.util.Map;
-import org.junit.jupiter.api.Test;
-
-/**
- * Testing the {@link RestApiHttpResponse}.
- */
-public class RestApiHttpResponseTest {
-    @Test
-    void testToJson() {
-        RestApiHttpResponse res = new RestApiHttpResponse(new DefaultHttpResponse(HttpVersion.HTTP_1_1, OK));
-
-        Map<?, String> value = Map.of(
-                "{root:{foo:foo,subCfg:{bar:bar}}}", "\"{root:{foo:foo,subCfg:{bar:bar}}}\"",
-                Map.of("err", new ErrorResult("t", "m")), "{\"err\":{\"type\":\"t\",\"message\":\"m\"}}"
-        );
-
-        for (Map.Entry<?, String> e : value.entrySet()) {
-            res.headers().clear();
-
-            String act = new String(res.json(e.getKey()).content(), UTF_8);
-
-            assertEquals(e.getValue(), act);
-            assertEquals(res.headers().get(CONTENT_TYPE), APPLICATION_JSON.toString());
-        }
-    }
-}
diff --git a/modules/rest/openapi/openapi.yaml b/modules/rest/openapi/openapi.yaml
new file mode 100644
index 000000000..cf7b681b1
--- /dev/null
+++ b/modules/rest/openapi/openapi.yaml
@@ -0,0 +1,145 @@
+openapi: 3.0.1
+info:
+  title: Ignite REST module
+  contact:
+    email: user@ignite.apache.org
+  license:
+    name: Apache 2.0
+    url: https://ignite.apache.org
+  version: 3.0.0-alpha
+paths:
+  /management/v1/cluster/init:
+    post:
+      tags:
+      - clusterManagement
+      operationId: init
+      parameters: []
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/InitCommand'
+        required: true
+      responses:
+        "200":
+          description: Cluster initialized
+        "409":
+          description: Cluster already initialized
+  /management/v1/configuration/cluster:
+    get:
+      tags:
+      - clusterConfiguration
+      operationId: getClusterConfiguration
+      parameters: []
+      responses:
+        "200":
+          description: Get cluster configuration
+          content:
+            text/plain:
+              schema:
+                type: string
+    patch:
+      tags:
+      - clusterConfiguration
+      operationId: updateClusterConfiguration
+      parameters: []
+      requestBody:
+        content:
+          text/plain:
+            schema:
+              type: string
+        required: true
+      responses:
+        "200":
+          description: Configuration updated
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Void'
+  /management/v1/configuration/cluster/{path}:
+    get:
+      tags:
+      - clusterConfiguration
+      operationId: getClusterConfigurationByPath
+      parameters:
+      - name: path
+        in: path
+        required: true
+        schema:
+          type: string
+      responses:
+        "200":
+          description: Configuration represented by path
+          content:
+            text/plain:
+              schema:
+                type: string
+  /management/v1/configuration/node:
+    get:
+      tags:
+      - nodeConfiguration
+      operationId: getNodeConfiguration
+      parameters: []
+      responses:
+        "200":
+          description: Whole node configuration
+          content:
+            text/plain:
+              schema:
+                type: string
+    patch:
+      tags:
+      - nodeConfiguration
+      operationId: updateNodeConfiguration
+      parameters: []
+      requestBody:
+        content:
+          text/plain:
+            schema:
+              type: string
+        required: true
+      responses:
+        "200":
+          description: Configuration updated
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Void'
+  /management/v1/configuration/node/{path}:
+    get:
+      tags:
+      - nodeConfiguration
+      operationId: getNodeConfigurationByPath
+      parameters:
+      - name: path
+        in: path
+        required: true
+        schema:
+          type: string
+      responses:
+        "200":
+          description: Configuration represented by path
+          content:
+            text/plain:
+              schema:
+                type: string
+components:
+  schemas:
+    InitCommand:
+      required:
+      - clusterName
+      - metaStorageNodes
+      type: object
+      properties:
+        metaStorageNodes:
+          type: array
+          items:
+            type: string
+        cmgNodes:
+          type: array
+          items:
+            type: string
+        clusterName:
+          type: string
+    Void:
+      type: object
diff --git a/modules/rest/pom.xml b/modules/rest/pom.xml
index cd924ee0c..8836e8ab1 100644
--- a/modules/rest/pom.xml
+++ b/modules/rest/pom.xml
@@ -45,52 +45,36 @@
 
         <dependency>
             <groupId>org.apache.ignite</groupId>
-            <artifactId>ignite-api</artifactId>
+            <artifactId>ignite-cluster-management</artifactId>
         </dependency>
 
         <dependency>
             <groupId>org.apache.ignite</groupId>
-            <artifactId>ignite-network</artifactId>
+            <artifactId>ignite-api</artifactId>
         </dependency>
 
         <!-- 3rd party dependencies -->
         <dependency>
-            <groupId>com.fasterxml.jackson.core</groupId>
-            <artifactId>jackson-databind</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>io.netty</groupId>
-            <artifactId>netty-common</artifactId>
+            <groupId>io.micronaut</groupId>
+            <artifactId>micronaut-inject</artifactId>
         </dependency>
 
         <dependency>
-            <groupId>io.netty</groupId>
-            <artifactId>netty-buffer</artifactId>
+            <groupId>io.micronaut</groupId>
+            <artifactId>micronaut-http-server-netty</artifactId>
         </dependency>
 
         <dependency>
-            <groupId>io.netty</groupId>
-            <artifactId>netty-codec</artifactId>
+            <groupId>io.micronaut.openapi</groupId>
+            <artifactId>micronaut-openapi</artifactId>
         </dependency>
 
         <dependency>
-            <groupId>io.netty</groupId>
-            <artifactId>netty-handler</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>io.netty</groupId>
-            <artifactId>netty-codec-http</artifactId>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
         </dependency>
 
         <!-- Test dependencies -->
-        <dependency>
-            <groupId>org.hamcrest</groupId>
-            <artifactId>hamcrest</artifactId>
-            <scope>test</scope>
-        </dependency>
-
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter-api</artifactId>
@@ -110,13 +94,6 @@
             <type>test-jar</type>
         </dependency>
 
-        <dependency>
-            <groupId>org.apache.ignite</groupId>
-            <artifactId>ignite-core</artifactId>
-            <scope>test</scope>
-            <type>test-jar</type>
-        </dependency>
-
         <!-- Logging in tests -->
         <dependency>
             <groupId>org.slf4j</groupId>
@@ -151,7 +128,20 @@
                             <artifactId>ignite-configuration-annotation-processor</artifactId>
                             <version>${project.version}</version>
                         </path>
+                        <path>
+                            <groupId>io.micronaut</groupId>
+                            <artifactId>micronaut-inject-java</artifactId>
+                            <version>${micronaut.version}</version>
+                        </path>
+                        <path>
+                            <groupId>io.micronaut.openapi</groupId>
+                            <artifactId>micronaut-openapi</artifactId>
+                            <version>${maven.micronaut.openapi.plugin.version}</version>
+                        </path>
                     </annotationProcessorPaths>
+                    <compilerArgs>
+                        <arg>-J-Dmicronaut.openapi.target.file=openapi/openapi.yaml</arg>
+                    </compilerArgs>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java b/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java
index f660e03d0..49891c692 100644
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java
+++ b/modules/rest/src/main/java/org/apache/ignite/internal/rest/RestComponent.java
@@ -17,151 +17,166 @@
 
 package org.apache.ignite.internal.rest;
 
-import io.netty.bootstrap.ServerBootstrap;
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelFuture;
-import io.netty.handler.logging.LogLevel;
-import io.netty.handler.logging.LoggingHandler;
+import io.micronaut.context.ApplicationContext;
+import io.micronaut.http.server.exceptions.ServerStartupException;
+import io.micronaut.openapi.annotation.OpenAPIInclude;
+import io.micronaut.runtime.Micronaut;
+import io.micronaut.runtime.exceptions.ApplicationStartupException;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.info.Contact;
+import io.swagger.v3.oas.annotations.info.Info;
+import io.swagger.v3.oas.annotations.info.License;
 import java.net.BindException;
-import java.net.InetSocketAddress;
-import java.util.function.Consumer;
+import java.util.List;
+import java.util.Map;
 import org.apache.ignite.configuration.schemas.rest.RestConfiguration;
 import org.apache.ignite.configuration.schemas.rest.RestView;
-import org.apache.ignite.internal.configuration.ConfigurationManager;
-import org.apache.ignite.internal.configuration.ConfigurationRegistry;
+import org.apache.ignite.internal.cluster.management.rest.ClusterManagementController;
 import org.apache.ignite.internal.manager.IgniteComponent;
-import org.apache.ignite.internal.rest.api.RestHandlersRegister;
-import org.apache.ignite.internal.rest.api.Routes;
-import org.apache.ignite.internal.rest.netty.RestApiInitializer;
-import org.apache.ignite.internal.rest.routes.Router;
-import org.apache.ignite.internal.rest.routes.SimpleRouter;
-import org.apache.ignite.lang.IgniteException;
+import org.apache.ignite.internal.rest.api.RestFactory;
+import org.apache.ignite.internal.rest.configuration.ClusterConfigurationController;
+import org.apache.ignite.internal.rest.configuration.NodeConfigurationController;
 import org.apache.ignite.lang.IgniteInternalException;
 import org.apache.ignite.lang.IgniteLogger;
-import org.apache.ignite.network.NettyBootstrapFactory;
+import org.jetbrains.annotations.Nullable;
 
 /**
- * Rest component is responsible for starting REST endpoints.
+ * Rest module is responsible for starting REST endpoints for accessing and managing configuration.
  *
- * <p>It is started on port 10300 by default, but it is possible to change this in configuration itself. Refer to default config file in
+ * <p>It is started on port 10300 by default but it is possible to change this in configuration itself. Refer to default config file in
  * resources for the example.
  */
-public class RestComponent implements RestHandlersRegister, IgniteComponent {
+@OpenAPIDefinition(info = @Info(title = "Ignite REST module", version = "3.0.0-alpha", license = @License(name = "Apache 2.0", url = "https://ignite.apache.org"), contact = @Contact(email = "user@ignite.apache.org")))
+@OpenAPIInclude(classes = {ClusterConfigurationController.class, NodeConfigurationController.class, ClusterManagementController.class})
+public class RestComponent implements IgniteComponent {
+    /** Default port. */
+    public static final int DFLT_PORT = 10300;
+
+    /** Server host. */
+    private static final String LOCALHOST = "localhost";
+
     /** Ignite logger. */
     private final IgniteLogger log = IgniteLogger.forClass(RestComponent.class);
 
-    /** Node configuration register. */
-    private final ConfigurationRegistry nodeCfgRegistry;
+    /** Factories that produce beans needed for REST controllers. */
+    private final List<RestFactory> restFactories;
 
-    /** Netty bootstrap factory. */
-    private final NettyBootstrapFactory bootstrapFactory;
+    private final RestConfiguration restConfiguration;
 
-    private final SimpleRouter router = new SimpleRouter();
+    /** Micronaut application context. */
+    private volatile ApplicationContext context;
 
-    /** Netty channel. */
-    private volatile Channel channel;
+    /** Server port. */
+    private int port;
 
     /**
      * Creates a new instance of REST module.
-     *
-     * @param nodeCfgMgr       Node configuration manager.
-     * @param bootstrapFactory Netty bootstrap factory.
      */
-    public RestComponent(ConfigurationManager nodeCfgMgr, NettyBootstrapFactory bootstrapFactory) {
-        nodeCfgRegistry = nodeCfgMgr.configurationRegistry();
-
-        this.bootstrapFactory = bootstrapFactory;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void registerHandlers(Consumer<Routes> registerAction) {
-        registerAction.accept(router);
+    public RestComponent(List<RestFactory> restFactories, RestConfiguration restConfiguration) {
+        this.restFactories = restFactories;
+        this.restConfiguration = restConfiguration;
     }
 
     /** {@inheritDoc} */
     @Override
     public void start() {
-        if (channel != null) {
-            throw new IgniteException("RestModule is already started.");
-        }
-
-        channel = startRestEndpoint(router).channel();
-    }
-
-    /**
-     * Start endpoint.
-     *
-     * @param router Dispatcher of http requests.
-     * @return Future which will be notified when this channel is closed.
-     * @throws RuntimeException if this module cannot be bound to a port.
-     */
-    private ChannelFuture startRestEndpoint(Router router) {
-        RestView restConfigurationView = nodeCfgRegistry.getConfiguration(RestConfiguration.KEY).value();
+        RestView restConfigurationView = restConfiguration.value();
 
         int desiredPort = restConfigurationView.port();
         int portRange = restConfigurationView.portRange();
 
-        int port = 0;
-        Channel ch = null;
-
-        ServerBootstrap bootstrap = bootstrapFactory.createServerBootstrap()
-                .handler(new LoggingHandler(LogLevel.INFO))
-                .childHandler(new RestApiInitializer(router));
-
         for (int portCandidate = desiredPort; portCandidate <= desiredPort + portRange; portCandidate++) {
-            ChannelFuture bindRes = bootstrap.bind(portCandidate).awaitUninterruptibly();
-
-            if (bindRes.isSuccess()) {
-                ch = bindRes.channel();
+            try {
                 port = portCandidate;
-                break;
-            } else if (!(bindRes.cause() instanceof BindException)) {
-                throw new RuntimeException(bindRes.cause());
+                context = buildMicronautContext(portCandidate).start();
+                log.info("REST protocol started successfully");
+                return;
+            } catch (ApplicationStartupException e) {
+                BindException bindException = findBindException(e);
+                if (bindException != null) {
+                    log.error("Got exception " + bindException + " during node start on port "
+                            + portCandidate + " , trying again");
+                    continue;
+                }
+                throw new RuntimeException(e);
             }
         }
 
-        if (ch == null) {
-            String msg = "Cannot start REST endpoint. "
-                    + "All ports in range [" + desiredPort + ", " + (desiredPort + portRange) + "] are in use.";
-
-            log.error(msg);
+        String msg = "Cannot start REST endpoint. " + "All ports in range [" + desiredPort + ", " + (desiredPort + portRange)
+                + "] are in use.";
+        log.error(msg);
+        throw new RuntimeException(msg);
+    }
 
-            throw new RuntimeException(msg);
+    @Nullable
+    private BindException findBindException(ApplicationStartupException e) {
+        var throwable = e.getCause();
+        while (throwable != null) {
+            if (throwable instanceof BindException) {
+                return (BindException) throwable;
+            }
+            throwable = throwable.getCause();
         }
+        return null;
+    }
 
-        if (log.isInfoEnabled()) {
-            log.info("REST protocol started successfully on port " + port);
+    private Micronaut buildMicronautContext(int portCandidate) {
+        Micronaut micronaut = Micronaut.build("");
+        setFactories(micronaut);
+        return micronaut
+                .properties(Map.of("micronaut.server.port", portCandidate))
+                .banner(false)
+                .mapError(ServerStartupException.class, this::mapServerStartupException)
+                .mapError(ApplicationStartupException.class, ex -> -1);
+    }
+
+    private void setFactories(Micronaut micronaut) {
+        for (var factory : restFactories) {
+            micronaut.singletons(factory);
         }
+    }
 
-        return ch.closeFuture();
+    private int mapServerStartupException(ServerStartupException exception) {
+        if (exception.getCause() instanceof BindException) {
+            return -1; // -1 forces the micronaut to throw an ApplicationStartupException
+        } else {
+            return 1;
+        }
     }
 
     /** {@inheritDoc} */
     @Override
-    public void stop() throws Exception {
+    public synchronized void stop() throws Exception {
         // TODO: IGNITE-16636 Use busy-lock approach to guard stopping RestComponent
-
-        if (channel != null) {
-            channel.close().await();
-
-            channel = null;
+        if (context != null) {
+            context.stop();
+            context = null;
         }
     }
 
     /**
-     * Returns the local address that the REST endpoint is bound to.
+     * Returns server port.
      *
-     * @return local REST address.
      * @throws IgniteInternalException if the component has not been started yet.
      */
-    public InetSocketAddress localAddress() {
-        Channel channel = this.channel;
+    public int port() {
+        if (context == null) {
+            throw new IgniteInternalException("RestComponent has not been started");
+        }
+
+        return port;
+    }
 
-        if (channel == null) {
+    /**
+     * Returns server host.
+     *
+     * @throws IgniteInternalException if the component has not been started yet.
+     */
+    public String host() {
+        if (context == null) {
             throw new IgniteInternalException("RestComponent has not been started");
         }
 
-        return (InetSocketAddress) channel.localAddress();
+        return LOCALHOST;
     }
 }
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/RestApiHandler.java b/modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/RestApiHandler.java
deleted file mode 100644
index a392ec221..000000000
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/RestApiHandler.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.rest.netty;
-
-import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
-import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
-import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE;
-import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;
-import static io.netty.handler.codec.http.HttpResponseStatus.OK;
-
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.EmptyByteBuf;
-import io.netty.buffer.Unpooled;
-import io.netty.buffer.UnpooledByteBufAllocator;
-import io.netty.channel.ChannelFuture;
-import io.netty.channel.ChannelFutureListener;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.SimpleChannelInboundHandler;
-import io.netty.handler.codec.http.DefaultFullHttpResponse;
-import io.netty.handler.codec.http.DefaultHttpResponse;
-import io.netty.handler.codec.http.EmptyHttpHeaders;
-import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.HttpResponseStatus;
-import io.netty.handler.codec.http.HttpUtil;
-import io.netty.handler.codec.http.HttpVersion;
-import java.util.concurrent.CompletableFuture;
-import org.apache.ignite.internal.rest.api.RestApiHttpResponse;
-import org.apache.ignite.internal.rest.routes.Router;
-import org.apache.ignite.lang.IgniteLogger;
-
-/**
- * Main handler of REST HTTP chain. It receives http request, process it by {@link Router} and produce http response.
- */
-public class RestApiHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
-    /** Ignite logger. */
-    private final IgniteLogger log = IgniteLogger.forClass(getClass());
-
-    /** Requests' router. */
-    private final Router router;
-
-    /**
-     * Creates a new instance of API handler.
-     *
-     * @param router Router.
-     */
-    public RestApiHandler(Router router) {
-        this.router = router;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
-        CompletableFuture<DefaultFullHttpResponse> responseFuture = router.route(request)
-                .map(route -> {
-                    var response = new RestApiHttpResponse(new DefaultHttpResponse(HttpVersion.HTTP_1_1, OK));
-
-                    return route.handle(request, response)
-                            .thenApply(resp -> {
-                                ByteBuf content = resp.content() != null
-                                        ? Unpooled.wrappedBuffer(resp.content())
-                                        : new EmptyByteBuf(UnpooledByteBufAllocator.DEFAULT);
-
-                                return new DefaultFullHttpResponse(
-                                        resp.protocolVersion(),
-                                        resp.status(),
-                                        content,
-                                        resp.headers(),
-                                        EmptyHttpHeaders.INSTANCE
-                                );
-                            });
-                })
-                .orElseGet(() -> CompletableFuture.completedFuture(
-                        new DefaultFullHttpResponse(request.protocolVersion(), HttpResponseStatus.NOT_FOUND)
-                ));
-
-        responseFuture
-                .whenCompleteAsync((response, e) -> {
-                    if (e != null) {
-                        exceptionCaught(ctx, e);
-
-                        return;
-                    }
-
-                    response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());
-
-                    boolean keepAlive = HttpUtil.isKeepAlive(request);
-
-                    if (keepAlive) {
-                        if (!request.protocolVersion().isKeepAliveDefault()) {
-                            response.headers().set(CONNECTION, KEEP_ALIVE);
-                        }
-                    } else {
-                        response.headers().set(CONNECTION, CLOSE);
-                    }
-
-                    ChannelFuture f = ctx.writeAndFlush(response);
-
-                    if (!keepAlive) {
-                        f.addListener(ChannelFutureListener.CLOSE);
-                    }
-                }, ctx.executor());
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
-        log.error("Failed to process http request:", cause);
-        var res = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR);
-        ctx.writeAndFlush(res).addListener(ChannelFutureListener.CLOSE);
-    }
-}
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/RestApiInitializer.java b/modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/RestApiInitializer.java
deleted file mode 100644
index 1fd3ccc6c..000000000
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/netty/RestApiInitializer.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.rest.netty;
-
-import io.netty.channel.ChannelInitializer;
-import io.netty.channel.ChannelPipeline;
-import io.netty.channel.socket.SocketChannel;
-import io.netty.handler.codec.http.HttpObjectAggregator;
-import io.netty.handler.codec.http.HttpServerCodec;
-import org.apache.ignite.internal.rest.routes.Router;
-
-/**
- * Initializes channel with needed handlers for HTTP processing.
- */
-public class RestApiInitializer extends ChannelInitializer<SocketChannel> {
-    /** Router. */
-    private final Router router;
-
-    /**
-     * Creates a new instance of initializer.
-     *
-     * @param router Router.
-     */
-    public RestApiInitializer(Router router) {
-        this.router = router;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void initChannel(SocketChannel ch) {
-        ChannelPipeline p = ch.pipeline();
-        p.addLast(new HttpServerCodec());
-        p.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
-        p.addLast(new RestApiHandler(router));
-    }
-}
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java b/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
old mode 100755
new mode 100644
index 81b40c399..2e2f9e6d6
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
+++ b/modules/rest/src/main/java/org/apache/ignite/internal/rest/package-info.java
@@ -16,7 +16,7 @@
  */
 
 /**
- * This package contains an implementation of Ignite component related to REST interface.
+ * This package contains REST component core implementation.
  */
 
 package org.apache.ignite.internal.rest;
diff --git a/modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/SimpleRouter.java b/modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/SimpleRouter.java
deleted file mode 100644
index c534085bf..000000000
--- a/modules/rest/src/main/java/org/apache/ignite/internal/rest/routes/SimpleRouter.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.rest.routes;
-
-import io.netty.handler.codec.http.HttpRequest;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import org.apache.ignite.internal.rest.api.Route;
-import org.apache.ignite.internal.rest.api.Routes;
-
-/**
- * Dispatcher of http requests.
- *
- * <p>Example:
- * <pre>
- * {@code
- * var router = new SimpleRouter();
- * router.get("/user", (req, resp) -> {
- *     resp.status(HttpResponseStatus.OK);
- * });
- * }
- * </pre>
- */
-public class SimpleRouter implements Router, Routes {
-    /** Routes. */
-    private final List<Route> routes;
-
-    /**
-     * Creates a new empty router.
-     */
-    public SimpleRouter() {
-        routes = new ArrayList<>();
-    }
-
-    /**
-     * Adds the route to router chain.
-     *
-     * @param route Route
-     */
-    @Override
-    public void addRoute(Route route) {
-        routes.add(route);
-    }
-
-    /**
-     * Finds the route by request.
-     *
-     * @param req Request.
-     * @return Route if founded.
-     */
-    @Override
-    public Optional<Route> route(HttpRequest req) {
-        return routes.stream().filter(r -> r.match(req)).findFirst();
-    }
-}
diff --git a/modules/rest/src/test/java/org/apache/ignite/internal/rest/routes/RouteTest.java b/modules/rest/src/test/java/org/apache/ignite/internal/rest/routes/RouteTest.java
deleted file mode 100644
index 8e0b8eaa0..000000000
--- a/modules/rest/src/test/java/org/apache/ignite/internal/rest/routes/RouteTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.rest.routes;
-
-import static io.netty.handler.codec.http.HttpMethod.GET;
-import static io.netty.handler.codec.http.HttpMethod.PATCH;
-import static io.netty.handler.codec.http.HttpMethod.PUT;
-import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import io.netty.handler.codec.http.DefaultHttpRequest;
-import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpHeaderValues;
-import java.util.concurrent.CompletableFuture;
-import org.apache.ignite.internal.rest.api.RequestHandler;
-import org.apache.ignite.internal.rest.api.Route;
-import org.junit.jupiter.api.Test;
-
-/**
- * Test class for {@link Route}.
- */
-public class RouteTest {
-    private static final RequestHandler NO_OP_HANDLER = (request, response) -> CompletableFuture.completedFuture(response);
-
-    @Test
-    void testMatchByUri() {
-        var route = new Route("/user", GET, null, NO_OP_HANDLER);
-        var req = new DefaultHttpRequest(HTTP_1_1, GET, "/user");
-        assertTrue(route.match(req));
-    }
-
-    @Test
-    void testNonMatchByUri() {
-        var route = new Route("/user", GET, null, NO_OP_HANDLER);
-        var req = new DefaultHttpRequest(HTTP_1_1, GET, "/user/1");
-        assertFalse(route.match(req));
-    }
-
-    @Test
-    void testMatchByContentTypeIfAcceptTypeEmpty() {
-        var route = new Route("/user", GET, null, NO_OP_HANDLER);
-        var req = new DefaultHttpRequest(HTTP_1_1, GET, "/user/");
-        req.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN);
-        assertTrue(route.match(req));
-    }
-
-    @Test
-    void testMatchByContentTypeIfAcceptTypeNonEmpty() {
-        var route = new Route("/user", PUT, "text/plain", NO_OP_HANDLER);
-        var req = new DefaultHttpRequest(HTTP_1_1, PUT, "/user");
-        req.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN);
-        assertTrue(route.match(req));
-    }
-
-    @Test
-    void testNonMatchByContentTypeIfAcceptTypeNonEmpty() {
-        var route = new Route("/user", PUT, "text/plain", NO_OP_HANDLER);
-        var req = new DefaultHttpRequest(HTTP_1_1, GET, "/user/");
-        req.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
-        assertFalse(route.match(req));
-    }
-
-    @Test
-    void testMatchByUriWithParams() {
-        var route = new Route("/user/:user", GET, null, NO_OP_HANDLER);
-        var req = new DefaultHttpRequest(HTTP_1_1, GET, "/user/John");
-        assertTrue(route.match(req));
-    }
-
-    @Test
-    void testPatchVerbMatch() {
-        var route = new Route("/user", PATCH, null, NO_OP_HANDLER);
-        var req = new DefaultHttpRequest(HTTP_1_1, PATCH, "/user");
-
-        assertTrue(route.match(req));
-    }
-}
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java
index b6ea675f0..ae937f635 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ItDistributedConfigurationPropertiesTest.java
@@ -26,7 +26,6 @@ import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.nullValue;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.mockito.Mockito.mock;
 
 import java.nio.file.Path;
 import java.util.List;
@@ -46,7 +45,6 @@ import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.internal.metastorage.MetaStorageManager;
 import org.apache.ignite.internal.metastorage.server.SimpleInMemoryKeyValueStorage;
 import org.apache.ignite.internal.raft.Loza;
-import org.apache.ignite.internal.rest.RestComponent;
 import org.apache.ignite.internal.testframework.WorkDirectory;
 import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
 import org.apache.ignite.internal.util.IgniteUtils;
@@ -117,7 +115,6 @@ public class ItDistributedConfigurationPropertiesTest {
                     vaultManager,
                     clusterService,
                     raftManager,
-                    mock(RestComponent.class),
                     new ConcurrentMapClusterStateStorage()
             );
 
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
index 2b90ddbbb..6711a3204 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ItDistributedConfigurationStorageTest.java
@@ -22,7 +22,6 @@ import static org.apache.ignite.internal.testframework.IgniteTestUtils.waitForCo
 import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willBe;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.equalTo;
-import static org.mockito.Mockito.mock;
 
 import java.io.Serializable;
 import java.nio.file.Path;
@@ -37,7 +36,6 @@ import org.apache.ignite.internal.manager.IgniteComponent;
 import org.apache.ignite.internal.metastorage.MetaStorageManager;
 import org.apache.ignite.internal.metastorage.server.SimpleInMemoryKeyValueStorage;
 import org.apache.ignite.internal.raft.Loza;
-import org.apache.ignite.internal.rest.RestComponent;
 import org.apache.ignite.internal.testframework.WorkDirectory;
 import org.apache.ignite.internal.testframework.WorkDirectoryExtension;
 import org.apache.ignite.internal.vault.VaultManager;
@@ -91,7 +89,6 @@ public class ItDistributedConfigurationStorageTest {
                     vaultManager,
                     clusterService,
                     raftManager,
-                    mock(RestComponent.class),
                     new ConcurrentMapClusterStateStorage()
             );
 
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
index d2ac1d9ec..3e90e4979 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItIgniteNodeRestartTest.java
@@ -70,7 +70,6 @@ import org.apache.ignite.internal.metastorage.server.persistence.RocksDbKeyValue
 import org.apache.ignite.internal.raft.Loza;
 import org.apache.ignite.internal.recovery.ConfigurationCatchUpListener;
 import org.apache.ignite.internal.recovery.RecoveryCompletionFutureFactory;
-import org.apache.ignite.internal.rest.RestComponent;
 import org.apache.ignite.internal.schema.SchemaManager;
 import org.apache.ignite.internal.sql.engine.SqlQueryProcessor;
 import org.apache.ignite.internal.storage.DataStorageManager;
@@ -231,7 +230,6 @@ public class ItIgniteNodeRestartTest extends IgniteAbstractTest {
                 vault,
                 clusterSvc,
                 raftMgr,
-                mock(RestComponent.class),
                 new RocksDbClusterStateStorage(dir.resolve("cmg"))
         );
 
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItNoThreadsLeftTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItNoThreadsLeftTest.java
index 2951a043d..0274546a9 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItNoThreadsLeftTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/ItNoThreadsLeftTest.java
@@ -85,7 +85,7 @@ public class ItNoThreadsLeftTest extends IgniteAbstractTest {
             stopNode(testInfo);
         }
 
-        boolean threadsKilled = waitForCondition(() -> threadsBefore.size() == getCurrentThreads().size(), 3_000);
+        boolean threadsKilled = waitForCondition(() -> getCurrentThreads().size() <= threadsBefore.size(), 3_000);
 
         if (!threadsKilled) {
             String leakedThreadNames = getCurrentThreads().stream()
diff --git a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
index df1408b56..9fd9b5f69 100644
--- a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
+++ b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
@@ -35,11 +35,13 @@ import org.apache.ignite.client.handler.ClientHandlerModule;
 import org.apache.ignite.compute.IgniteCompute;
 import org.apache.ignite.configuration.schemas.compute.ComputeConfiguration;
 import org.apache.ignite.configuration.schemas.network.NetworkConfiguration;
+import org.apache.ignite.configuration.schemas.rest.RestConfiguration;
 import org.apache.ignite.configuration.schemas.table.TablesConfiguration;
 import org.apache.ignite.internal.baseline.BaselineManager;
 import org.apache.ignite.internal.cluster.management.ClusterManagementGroupManager;
 import org.apache.ignite.internal.cluster.management.network.messages.CmgMessagesSerializationRegistryInitializer;
 import org.apache.ignite.internal.cluster.management.raft.RocksDbClusterStateStorage;
+import org.apache.ignite.internal.cluster.management.rest.ClusterManagementRestFactory;
 import org.apache.ignite.internal.components.LongJvmPauseDetector;
 import org.apache.ignite.internal.compute.ComputeComponent;
 import org.apache.ignite.internal.compute.ComputeComponentImpl;
@@ -50,7 +52,6 @@ import org.apache.ignite.internal.configuration.ConfigurationModule;
 import org.apache.ignite.internal.configuration.ConfigurationModules;
 import org.apache.ignite.internal.configuration.ConfigurationRegistry;
 import org.apache.ignite.internal.configuration.ServiceLoaderModulesProvider;
-import org.apache.ignite.internal.configuration.rest.ConfigurationHttpHandlers;
 import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
 import org.apache.ignite.internal.configuration.storage.DistributedConfigurationStorage;
 import org.apache.ignite.internal.configuration.storage.LocalConfigurationStorage;
@@ -60,6 +61,8 @@ import org.apache.ignite.internal.raft.Loza;
 import org.apache.ignite.internal.recovery.ConfigurationCatchUpListener;
 import org.apache.ignite.internal.recovery.RecoveryCompletionFutureFactory;
 import org.apache.ignite.internal.rest.RestComponent;
+import org.apache.ignite.internal.rest.api.RestFactory;
+import org.apache.ignite.internal.rest.configuration.PresentationsFactory;
 import org.apache.ignite.internal.schema.SchemaManager;
 import org.apache.ignite.internal.sql.api.IgniteSqlImpl;
 import org.apache.ignite.internal.sql.engine.QueryProcessor;
@@ -230,8 +233,6 @@ public class IgniteImpl implements Ignite {
 
         nettyBootstrapFactory = new NettyBootstrapFactory(networkConfiguration, clusterLocalConfiguration.getName());
 
-        restComponent = new RestComponent(nodeCfgMgr, nettyBootstrapFactory);
-
         clusterSvc = new ScaleCubeClusterServiceFactory().createClusterService(
                 clusterLocalConfiguration,
                 networkConfiguration,
@@ -252,7 +253,6 @@ public class IgniteImpl implements Ignite {
                 vaultMgr,
                 clusterSvc,
                 raftMgr,
-                restComponent,
                 new RocksDbClusterStateStorage(workDir.resolve(CMG_DB_PATH))
         );
 
@@ -274,6 +274,11 @@ public class IgniteImpl implements Ignite {
                 modules.distributed().polymorphicSchemaExtensions()
         );
 
+        RestFactory factory = new PresentationsFactory(nodeCfgMgr, clusterCfgMgr);
+        RestFactory clusterManagementRestFactory = new ClusterManagementRestFactory(clusterSvc);
+        RestConfiguration restConfiguration = nodeCfgMgr.configurationRegistry().getConfiguration(RestConfiguration.KEY);
+        restComponent = new RestComponent(List.of(factory, clusterManagementRestFactory), restConfiguration);
+
         baselineMgr = new BaselineManager(
                 clusterCfgMgr,
                 metaStorageMgr,
@@ -334,8 +339,6 @@ public class IgniteImpl implements Ignite {
                 sql
         );
 
-        new ConfigurationHttpHandlers(nodeCfgMgr, clusterCfgMgr).registerHandlers(restComponent);
-
         longJvmPauseDetector = new LongJvmPauseDetector(name);
     }
 
@@ -586,7 +589,7 @@ public class IgniteImpl implements Ignite {
      */
     // TODO: should be encapsulated in local properties, see https://issues.apache.org/jira/browse/IGNITE-15131
     public NetworkAddress restAddress() {
-        return NetworkAddress.from(restComponent.localAddress());
+        return new NetworkAddress(restComponent.host(), restComponent.port());
     }
 
     /**
@@ -603,7 +606,7 @@ public class IgniteImpl implements Ignite {
      * Initializes the cluster that this node is present in.
      *
      * @param metaStorageNodeNames names of nodes that will host the Meta Storage.
-     * @param cmgNodeNames names of nodes that will host the CMG.
+     * @param cmgNodeNames         names of nodes that will host the CMG.
      * @param clusterName Human-readable name of a cluster.
      * @throws NodeStoppingException If node stopping intention was detected.
      */
diff --git a/parent/pom.xml b/parent/pom.xml
index 287633cae..76bb96b5e 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -53,6 +53,7 @@
         <asm.framework.version>9.0</asm.framework.version>
         <compile.testing.library.version>0.19</compile.testing.library.version>
         <jackson.version>2.13.1</jackson.version>
+        <jakarta.annotations.version>2.0.0</jakarta.annotations.version>
         <jansi.version>1.18</jansi.version>
         <netty.version>4.1.70.Final</netty.version>
         <javapoet.version>1.13.0</javapoet.version>
@@ -60,12 +61,13 @@
         <jetbrains.annotations.version>20.1.0</jetbrains.annotations.version>
         <jmh.framework.version>1.35</jmh.framework.version>
         <junit.version>5.8.1</junit.version>
-        <micronaut.version>2.1.2</micronaut.version>
-        <micronaut.test.junit5.version>2.3.1</micronaut.test.junit5.version>
+        <micronaut.version>3.4.1</micronaut.version>
+        <micronaut.test.junit5.version>3.3.0</micronaut.test.junit5.version>
         <mockito.version>4.3.1</mockito.version>
         <picocli.version>4.5.2</picocli.version>
         <slf4j.version>1.7.32</slf4j.version>
         <spoon.framework.version>8.4.0-beta-18</spoon.framework.version>
+        <swagger.annotations.version>2.1.12</swagger.annotations.version>
         <typesafe.version>1.4.1</typesafe.version>
         <hamcrest.version>2.2</hamcrest.version>
         <hamcrest.optional.version>2.0.0</hamcrest.optional.version>
@@ -113,7 +115,7 @@
         <maven.shade.plugin.version>3.2.4</maven.shade.plugin.version>
         <maven.source.plugin.version>3.2.1</maven.source.plugin.version>
         <maven.surefire.plugin.version>3.0.0-M5</maven.surefire.plugin.version>
-
+        <maven.micronaut.openapi.plugin.version>3.2.0</maven.micronaut.openapi.plugin.version>
         <!-- We leave it empty so that, when jacoco does not run, the unreplaced @{argLine} does not impede the build. -->
         <additional.test.argLine/>
         <common.test.argLine>
@@ -479,6 +481,55 @@
                 <version>${micronaut.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>io.micronaut</groupId>
+                <artifactId>micronaut-inject</artifactId>
+                <version>${micronaut.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>io.micronaut</groupId>
+                <artifactId>micronaut-http</artifactId>
+                <version>${micronaut.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>io.micronaut</groupId>
+                <artifactId>micronaut-http-server</artifactId>
+                <version>${micronaut.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>io.micronaut</groupId>
+                <artifactId>micronaut-http-server-netty</artifactId>
+                <version>${micronaut.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>io.micronaut.openapi</groupId>
+                <artifactId>micronaut-openapi</artifactId>
+                <version>${maven.micronaut.openapi.plugin.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>jakarta.annotation</groupId>
+                <artifactId>jakarta.annotation-api</artifactId>
+                <scope>compile</scope>
+                <version>${jakarta.annotations.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>jakarta.inject</groupId>
+                <artifactId>jakarta.inject-api</artifactId>
+                <version>${jakarta.annotations.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>io.swagger.core.v3</groupId>
+                <artifactId>swagger-annotations</artifactId>
+                <version>${swagger.annotations.version}</version>
+            </dependency>
+
             <dependency>
                 <groupId>com.typesafe</groupId>
                 <artifactId>config</artifactId>
@@ -613,6 +664,13 @@
                 <version>${micronaut.test.junit5.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>io.micronaut</groupId>
+                <artifactId>micronaut-http-client</artifactId>
+                <version>${micronaut.version}</version>
+                <scope>test</scope>
+            </dependency>
+
             <dependency>
                 <groupId>com.google.testing.compile</groupId>
                 <artifactId>compile-testing</artifactId>
@@ -1338,6 +1396,7 @@
                         <exclude>docs/_plugins/asciidoctor-extensions.rb</exclude> <!-- MIT license -->
                         <exclude>docs/_sass/rouge-base16-solarized.scss</exclude> <!-- MIT license. -->
                         <exclude>**/*.json</exclude> <!-- Files in JSON format -->
+                        <exclude>modules/rest/openapi/openapi.yaml</exclude> <!-- Autogenerated Open API spec -->
                         <exclude>**/*.drawio</exclude> <!-- Draw.IO diagrams -->
                         <exclude>modules/cli/src/**/resources//builtin_modules.conf</exclude> <!-- CLI configuration files -->
                         <exclude>**/*.iml</exclude> <!-- IntelliJ IDEA project files -->