You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sk...@apache.org on 2022/08/24 07:58:03 UTC

[ignite-3] branch main updated: IGNITE-17349 Added common UI components. Fixes #986

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

sk0x50 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 ad625c51a4 IGNITE-17349 Added common UI components. Fixes #986
ad625c51a4 is described below

commit ad625c51a4084f0ced38b645d4aafdb0b8157424
Author: Vadim Pakhnushev <86...@users.noreply.github.com>
AuthorDate: Wed Aug 24 10:57:23 2022 +0300

    IGNITE-17349 Added common UI components. Fixes #986
    
    Signed-off-by: Slava Koptilin <sl...@gmail.com>
---
 .../ItUpdateConfigurationCallTest.java             |   2 +-
 .../ItClusterConfigCommandNotInitializedTest.java  |  42 +++++++
 .../cli/commands/connect/ItConnectCommandTest.java |   3 +-
 .../ignite/cli/commands/sql/ItSqlCommandTest.java  |   3 +-
 .../cli/deprecated/ItClusterCommandTest.java       |   2 +-
 .../ignite/cli/deprecated/ItConfigCommandTest.java |  10 +-
 .../ignite/rest/ItGeneratedRestClientTest.java     |  12 ++
 .../ignite/cli/call/cluster/ClusterInitCall.java   |   3 +-
 .../configuration/ClusterConfigUpdateCall.java     |   2 +-
 .../call/configuration/NodeConfigUpdateCall.java   |   2 +-
 .../ignite/cli/call/connect/ConnectCall.java       |   5 +-
 .../ignite/cli/call/connect/DisconnectCall.java    |   7 +-
 .../ignite/cli/commands/OptionsConstants.java      |  24 ++--
 .../commands/cliconfig/CliConfigGetSubCommand.java |   2 +-
 .../commands/cliconfig/CliConfigSetSubCommand.java |   2 +-
 .../commands/cliconfig/CliConfigSubCommand.java    |   3 +-
 .../profile/CliConfigCreateProfileCommand.java     |   8 +-
 .../cliconfig/profile/CliConfigProfileCommand.java |   4 +-
 .../profile/CliConfigShowProfileCommand.java       |   3 +-
 .../cli/commands/cluster/ClusterCommand.java       |   2 +-
 .../cli/commands/cluster/ClusterReplCommand.java   |   2 +-
 .../config/ClusterConfigReplSubCommand.java        |   2 +-
 .../config/ClusterConfigShowReplSubCommand.java    |  33 +-----
 .../config/ClusterConfigShowSubCommand.java        |  11 +-
 .../cluster/config/ClusterConfigSubCommand.java    |   3 +-
 .../config/ClusterConfigUpdateReplSubCommand.java  |  10 +-
 .../config/ClusterConfigUpdateSubCommand.java      |  10 +-
 .../cluster/init/ClusterInitReplSubCommand.java    |  11 +-
 .../cluster/init/ClusterInitSubCommand.java        |   7 +-
 .../status/ClusterStatusReplSubCommand.java        |  13 +-
 .../cluster/status/ClusterStatusSubCommand.java    |   9 +-
 .../cli/commands/connect/ConnectCommand.java       |  10 +-
 .../cli/commands/connect/DisconnectCommand.java    |   2 +-
 .../ignite/cli/commands/node/NodeCommand.java      |   3 +-
 .../ignite/cli/commands/node/NodeReplCommand.java  |   2 +-
 .../node/config/NodeConfigReplSubCommand.java      |   3 +-
 .../node/config/NodeConfigShowSubCommand.java      |  13 +-
 .../commands/node/config/NodeConfigSubCommand.java |   3 +-
 .../node/config/NodeConfigUpdateSubCommand.java    |  10 +-
 .../node/status/NodeStatusReplSubCommand.java      |  13 +-
 .../commands/node/status/NodeStatusSubCommand.java |   9 +-
 .../questions/ConnectToClusterQuestion.java        |  27 +++--
 .../apache/ignite/cli/commands/sql/SqlCommand.java |  12 +-
 .../ignite/cli/commands/sql/SqlReplCommand.java    |  10 +-
 .../commands/sql/SqlReplTopLevelCliCommand.java    |   2 +-
 .../topology/LogicalTopologyReplSubCommand.java    |  12 +-
 .../topology/LogicalTopologySubCommand.java        |   6 +-
 .../topology/PhysicalTopologyReplSubCommand.java   |  12 +-
 .../topology/PhysicalTopologySubCommand.java       |   6 +-
 .../cli/commands/topology/TopologyCommand.java     |  11 +-
 .../cli/commands/topology/TopologyReplCommand.java |  11 +-
 .../cli/commands/version/VersionCommand.java       |   3 +-
 .../config/ini/SectionAlreadyExistsException.java  |   2 +-
 .../cli/core/call/CallExecutionPipeline.java       |  29 ++---
 .../cli/core/decorator/DecoratorRegistry.java      |   2 +-
 .../cli/core/exception/ExceptionHandler.java       |  10 +-
 .../handler/ConfigStoringExceptionHandler.java     |  10 +-
 .../handler/IgniteCliApiExceptionHandler.java      |  53 +++++++--
 .../handler/IgniteCliExceptionHandler.java         |   6 +-
 .../handler/PicocliExecutionExceptionHandler.java  |   2 +-
 .../handler/ProfileNotFoundExceptionHandler.java   |   6 +-
 .../SectionAlreadyExistsExceptionHandler.java      |   6 +-
 .../handler/ShowConfigExceptionHandler.java        |  49 ++++++++
 .../exception/handler/SqlExceptionHandler.java     |  23 ++--
 .../exception/handler/TimeoutExceptionHandler.java |  10 +-
 .../handler/UnknownCommandExceptionHandler.java    |   5 +-
 .../cli/core/flow/builder/FlowBuilderImpl.java     |   2 +-
 .../apache/ignite/cli/core/flow/builder/Flows.java |  17 ++-
 .../ignite/cli/core/style/AnsiStringSupport.java   |  41 ++++++-
 .../style/component/CommonMessages.java}           |  26 ++--
 .../cli/core/style/component/ErrorUiComponent.java | 132 +++++++++++++++++++++
 .../core/style/component/MessageUiComponent.java   |  95 +++++++++++++++
 .../core/style/component/QuestionUiComponent.java  |  71 +++++++++++
 .../style/component/UiComponent.java}              |  10 +-
 .../style/element/MarkedUiElement.java}            |  29 +++--
 .../style/element/UiElement.java}                  |  11 +-
 .../style/element/UiElements.java}                 |  36 +++---
 .../style/element/UiString.java}                   |  23 ++--
 .../decorators/ClusterStatusDecorator.java         |   3 +-
 .../decorators/DefaultDecorator.java               |   3 +-
 .../decorators/DefaultDecoratorRegistry.java       |   3 +-
 .../{commands => }/decorators/JsonDecorator.java   |   3 +-
 .../decorators/NodeStatusDecorator.java            |   3 +-
 .../decorators/ProfileDecorator.java               |   2 +-
 .../decorators/SqlQueryResultDecorator.java        |   3 +-
 .../{commands => }/decorators/TableDecorator.java  |   3 +-
 .../decorators/TopologyDecorator.java              |   3 +-
 .../builtins/init/InitIgniteCommand.java           |  46 ++++---
 .../cli/deprecated/spec/NodeCommandSpec.java       |  59 +++++----
 .../ignite/cli/deprecated/ui/ProgressBar.java      |   4 +-
 .../org/apache/ignite/cli/sql/SqlQueryResult.java  |   3 +-
 .../cli/commands/UrlOptionsNegativeTest.java       |  11 +-
 .../apache/ignite/cli/commands/flow/FlowTest.java  |  71 +++++++++++
 .../cli/commands/flow/TestExceptionHandler.java}   |  18 ++-
 .../ignite/cli/commands/flow/ThrowingStrCall.java} |  16 +--
 .../core/style/component/ErrorUiComponentTest.java |  81 +++++++++++++
 .../style/component/MessageUiComponentTest.java    |  69 +++++++++++
 .../cli/deprecated/IgniteCliInterfaceTest.java     |  48 ++++----
 .../ignite/cli/deprecated/ui/ProgressBarTest.java  |   4 +-
 .../ConfigurationValidationException.java          |   8 +-
 .../ConfigurationControllerBaseTest.java           |  18 +--
 .../java/org/apache/ignite/lang/ErrorGroup.java    |  21 +++-
 .../org/apache/ignite/lang/ErrorGroupTest.java     |  49 ++++++++
 .../apache/ignite/internal/rest/api/Problem.java   |  23 +++-
 .../internal/rest/api/ValidationProblem.java       | 111 -----------------
 .../exception/handler/IgniteExceptionHandler.java  |  22 +++-
 .../handler/IgniteExceptionHandlerTest.java        |  12 +-
 .../rest/ItInitializedClusterRestTest.java         |  15 +++
 108 files changed, 1257 insertions(+), 616 deletions(-)

diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItUpdateConfigurationCallTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItUpdateConfigurationCallTest.java
index 36286753b7..b1d0ec09fe 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItUpdateConfigurationCallTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/call/configuration/ItUpdateConfigurationCallTest.java
@@ -54,7 +54,7 @@ public class ItUpdateConfigurationCallTest extends CallInitializedIntegrationTes
         // Then
         assertThat(output.hasError()).isFalse();
         // And
-        assertThat(output.body()).contains("Cluster configuration was updated successfully.");
+        assertThat(output.body()).contains("Cluster configuration was updated successfully");
         // And buffer size is updated
         String updatedConfigurationProperty = readConfigurationProperty("rocksDb.defaultRegion.writeBufferSize");
         assertThat(updatedConfigurationProperty).isEqualTo("1024");
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/config/ItClusterConfigCommandNotInitializedTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/config/ItClusterConfigCommandNotInitializedTest.java
new file mode 100644
index 0000000000..072bab833c
--- /dev/null
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/cluster/config/ItClusterConfigCommandNotInitializedTest.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.cli.commands.cluster.config;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import org.apache.ignite.cli.commands.CliCommandTestNotInitializedIntegrationBase;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link ClusterConfigSubCommand} for the cluster that is not initialized.
+ */
+class ItClusterConfigCommandNotInitializedTest extends CliCommandTestNotInitializedIntegrationBase {
+    @Test
+    @DisplayName("Should print error message when run cluster config show on not initialized cluster")
+    void printStatus() {
+        execute("cluster", "config", "show", "--cluster-endpoint-url", NODE_URL);
+
+        assertAll(
+                this::assertOutputIsEmpty,
+                () -> assertExitCodeIs(1),
+                () -> assertErrOutputContains("Cannot show cluster config" + System.lineSeparator()
+                + "Probably, you have not initialized the cluster, try to run cluster init command")
+        );
+    }
+}
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/connect/ItConnectCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/connect/ItConnectCommandTest.java
index d49e0699fe..c1fc2ba388 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/connect/ItConnectCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/connect/ItConnectCommandTest.java
@@ -78,7 +78,8 @@ class ItConnectCommandTest extends CliCommandTestInitializedIntegrationBase {
 
         // Then
         assertAll(
-                () -> assertErrOutputIs("Could not connect to URL [url=http://localhost:11111]" + System.lineSeparator())
+                () -> assertErrOutputIs("Node unavailable" + System.lineSeparator()
+                        + "Could not connect to node with URL http://localhost:11111" + System.lineSeparator())
         );
         // And prompt is
         String prompt = Ansi.OFF.string(promptProvider.getPrompt());
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/sql/ItSqlCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/sql/ItSqlCommandTest.java
index 861e9f8b34..b0d2ce5253 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/sql/ItSqlCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/commands/sql/ItSqlCommandTest.java
@@ -77,7 +77,8 @@ class ItSqlCommandTest extends CliCommandTestInitializedIntegrationBase {
                 () -> assertExitCodeIs(1),
                 this::assertOutputIsEmpty,
                 // TODO: https://issues.apache.org/jira/browse/IGNITE-17090
-                () -> assertErrOutputIs("SQL query parsing error: Sql query execution failed." + System.lineSeparator())
+                () -> assertErrOutputIs("SQL query parsing error" + System.lineSeparator()
+                        + "Sql query execution failed." + System.lineSeparator())
         );
     }
 }
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/ItClusterCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/ItClusterCommandTest.java
index a8aaaf2f8b..9ca94bad23 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/ItClusterCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/ItClusterCommandTest.java
@@ -181,7 +181,7 @@ class ItClusterCommandTest extends AbstractCliIntegrationTest {
                 String.format("Wrong exit code; std is '%s', stderr is '%s'", out.toString(UTF_8), err.toString(UTF_8)),
                 exitCode, is(0)
         );
-        assertThat(out.toString(UTF_8), is("Cluster was initialized successfully." + NL));
+        assertThat(out.toString(UTF_8), is("Cluster was initialized successfully" + NL));
 
         // TODO: when IGNITE-16526 is implemented, also check that the logical topology contains all 4 nodes
     }
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/ItConfigCommandTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/ItConfigCommandTest.java
index c0aad09f5f..ea2c4ca0cc 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/ItConfigCommandTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/cli/deprecated/ItConfigCommandTest.java
@@ -21,9 +21,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.ignite.internal.testframework.IgniteTestUtils.testNodeName;
 import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.both;
 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;
 
@@ -90,7 +88,7 @@ public class ItConfigCommandTest extends AbstractCliIntegrationTest {
         assertEquals(0, exitCode);
         assertThat(
                 out.toString(UTF_8),
-                containsString("Node configuration was updated successfully.")
+                containsString("Node configuration was updated successfully")
 
         );
 
@@ -126,8 +124,7 @@ public class ItConfigCommandTest extends AbstractCliIntegrationTest {
         assertEquals(1, exitCode);
         assertThat(
                 err.toString(UTF_8),
-                both(startsWith("An error occurred"))
-                        .and(containsString("'network' configuration doesn't have the 'foo' sub-configuration"))
+                containsString("'network' configuration doesn't have the 'foo' sub-configuration")
         );
 
         resetStreams();
@@ -144,8 +141,7 @@ public class ItConfigCommandTest extends AbstractCliIntegrationTest {
         assertEquals(1, exitCode);
         assertThat(
                 err.toString(UTF_8),
-                both(containsString("An error occurred"))
-                        .and(containsString("'long' is expected as a type for the 'network.shutdownQuietPeriod' configuration value"))
+                containsString("'long' is expected as a type for the 'network.shutdownQuietPeriod' configuration value")
         );
     }
 
diff --git a/modules/cli/src/integrationTest/java/org/apache/ignite/rest/ItGeneratedRestClientTest.java b/modules/cli/src/integrationTest/java/org/apache/ignite/rest/ItGeneratedRestClientTest.java
index ed0b89ee0f..5f2619cff6 100644
--- a/modules/cli/src/integrationTest/java/org/apache/ignite/rest/ItGeneratedRestClientTest.java
+++ b/modules/cli/src/integrationTest/java/org/apache/ignite/rest/ItGeneratedRestClientTest.java
@@ -242,6 +242,18 @@ public class ItGeneratedRestClientTest {
         });
     }
 
+    @Test
+    void updateNodeConfigurationWithInvalidParam() throws JsonProcessingException {
+        ApiException thrown = assertThrows(
+                ApiException.class,
+                () -> clusterConfigurationApi.updateClusterConfiguration("rocksDb.defaultRegion.cache=invalid")
+        );
+
+        Problem problem = objectMapper.readValue(thrown.getResponseBody(), Problem.class);
+        assertThat(problem.getStatus(), equalTo(400));
+        assertThat(problem.getInvalidParams(), hasSize(1));
+    }
+
     @Test
     void initCluster() {
         assertDoesNotThrow(() -> {
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/ClusterInitCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/ClusterInitCall.java
index ec0f86296b..dbdc0474fc 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/ClusterInitCall.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/cluster/ClusterInitCall.java
@@ -32,7 +32,6 @@ import org.apache.ignite.rest.client.model.InitCommand;
  */
 @Singleton
 public class ClusterInitCall implements Call<ClusterInitCallInput, String> {
-
     /** {@inheritDoc} */
     @Override
     public DefaultCallOutput<String> execute(ClusterInitCallInput input) {
@@ -44,7 +43,7 @@ public class ClusterInitCall implements Call<ClusterInitCallInput, String> {
                     .cmgNodes(input.getCmgNodes())
                     .clusterName(input.getClusterName())
             );
-            return DefaultCallOutput.success("Cluster was initialized successfully.");
+            return DefaultCallOutput.success("Cluster was initialized successfully");
         } catch (ApiException | IllegalArgumentException e) {
             return DefaultCallOutput.failure(new IgniteCliApiException(e, input.getClusterUrl()));
         }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/ClusterConfigUpdateCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/ClusterConfigUpdateCall.java
index 52770eb25a..0481c872e0 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/ClusterConfigUpdateCall.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/ClusterConfigUpdateCall.java
@@ -46,7 +46,7 @@ public class ClusterConfigUpdateCall implements Call<ClusterConfigUpdateCallInpu
     private DefaultCallOutput<String> updateClusterConfig(ClusterConfigurationApi api, ClusterConfigUpdateCallInput input)
             throws ApiException {
         api.updateClusterConfiguration(input.getConfig());
-        return DefaultCallOutput.success("Cluster configuration was updated successfully.");
+        return DefaultCallOutput.success("Cluster configuration was updated successfully");
     }
 
     private ClusterConfigurationApi createApiClient(ClusterConfigUpdateCallInput input) {
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/NodeConfigUpdateCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/NodeConfigUpdateCall.java
index 3824411555..a568bcf9ed 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/NodeConfigUpdateCall.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/configuration/NodeConfigUpdateCall.java
@@ -46,7 +46,7 @@ public class NodeConfigUpdateCall implements Call<NodeConfigUpdateCallInput, Str
     private DefaultCallOutput<String> updateNodeConfig(NodeConfigurationApi api, NodeConfigUpdateCallInput input)
             throws ApiException {
         api.updateNodeConfiguration(input.getConfig());
-        return DefaultCallOutput.success("Node configuration was updated successfully.");
+        return DefaultCallOutput.success("Node configuration was updated successfully");
     }
 
     private NodeConfigurationApi createApiClient(NodeConfigUpdateCallInput input) {
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/connect/ConnectCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/connect/ConnectCall.java
index bf72de7750..6aaf320516 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/call/connect/ConnectCall.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/connect/ConnectCall.java
@@ -29,6 +29,8 @@ import org.apache.ignite.cli.core.call.DefaultCallOutput;
 import org.apache.ignite.cli.core.exception.IgniteCliApiException;
 import org.apache.ignite.cli.core.repl.Session;
 import org.apache.ignite.cli.core.repl.config.RootConfig;
+import org.apache.ignite.cli.core.style.component.MessageUiComponent;
+import org.apache.ignite.cli.core.style.element.UiElements;
 import org.apache.ignite.rest.client.api.NodeConfigurationApi;
 import org.apache.ignite.rest.client.api.NodeManagementApi;
 import org.apache.ignite.rest.client.invoker.ApiException;
@@ -40,7 +42,6 @@ import org.apache.ignite.rest.client.invoker.Configuration;
  */
 @Singleton
 public class ConnectCall implements Call<ConnectCallInput, String> {
-
     private final Session session;
 
     private final StateConfigProvider stateConfigProvider;
@@ -61,7 +62,7 @@ public class ConnectCall implements Call<ConnectCallInput, String> {
             session.setJdbcUrl(constructJdbcUrl(configuration, nodeUrl));
             session.setConnectedToNode(true);
 
-            return DefaultCallOutput.success("Connected to " + nodeUrl);
+            return DefaultCallOutput.success(MessageUiComponent.fromMessage("Connected to %s", UiElements.url(nodeUrl)).render());
 
         } catch (ApiException | IllegalArgumentException e) {
             session.setConnectedToNode(false);
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/call/connect/DisconnectCall.java b/modules/cli/src/main/java/org/apache/ignite/cli/call/connect/DisconnectCall.java
index e6c5404a85..bb60ee059e 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/call/connect/DisconnectCall.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/call/connect/DisconnectCall.java
@@ -24,13 +24,14 @@ import org.apache.ignite.cli.core.call.CallOutput;
 import org.apache.ignite.cli.core.call.DefaultCallOutput;
 import org.apache.ignite.cli.core.call.EmptyCallInput;
 import org.apache.ignite.cli.core.repl.Session;
+import org.apache.ignite.cli.core.style.component.MessageUiComponent;
+import org.apache.ignite.cli.core.style.element.UiElements;
 
 /**
  * Call for disconnect.
  */
 @Singleton
 public class DisconnectCall implements Call<EmptyCallInput, String> {
-
     @Inject
     private final Session session;
 
@@ -46,7 +47,9 @@ public class DisconnectCall implements Call<EmptyCallInput, String> {
             session.setNodeName(null);
             session.setConnectedToNode(false);
 
-            return DefaultCallOutput.success("Disconnected from " + nodeUrl);
+            return DefaultCallOutput.success(
+                    MessageUiComponent.fromMessage("Disconnected from %s", UiElements.url(nodeUrl)).render()
+            );
         }
 
         return DefaultCallOutput.empty();
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/OptionsConstants.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/OptionsConstants.java
index 934e72d2ad..5cd52a3b7c 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/OptionsConstants.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/OptionsConstants.java
@@ -23,28 +23,18 @@ import org.apache.ignite.cli.config.ConfigConstants;
  * Constants to use in {@code Option} annotations for commands.
  */
 public class OptionsConstants {
-    /**
-     * Cluster endpoint URL option name.
-     */
+    /** Cluster endpoint URL option name. */
     public static final String CLUSTER_URL_OPTION = "--cluster-endpoint-url";
 
-    /**
-     * Cluster endpoint URL option description.
-     */
-    public static final String CLUSTER_URL_DESC = "URL of cluster endpoint.";
+    /** Cluster endpoint URL option description. */
+    public static final String CLUSTER_URL_DESC = "URL of cluster endpoint";
 
-    /**
-     * Cluster endpoint URL option description key.
-     */
+    /** Cluster endpoint URL option description key. */
     public static final String CLUSTER_URL_KEY = ConfigConstants.CLUSTER_URL;
 
-    /**
-     * Node URL option name.
-     */
+    /** Node URL option name. */
     public static final String NODE_URL_OPTION = "--node-url";
 
-    /**
-     * Node URL option description.
-     */
-    public static final String NODE_URL_DESC = "URL of ignite node.";
+    /** Node URL option description. */
+    public static final String NODE_URL_DESC = "URL of ignite node";
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommand.java
index 46d9388ceb..2b43922113 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigGetSubCommand.java
@@ -35,7 +35,7 @@ public class CliConfigGetSubCommand extends BaseCommand implements Callable<Inte
     @Parameters
     private String key;
 
-    @Option(names = {"--profile", "-p"}, description = "Get property from specified profile.")
+    @Option(names = {"--profile", "-p"}, description = "Get property from specified profile")
     private String profileName;
 
     @Inject
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommand.java
index 1bfc8bba64..7fafe3c9fa 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSetSubCommand.java
@@ -36,7 +36,7 @@ public class CliConfigSetSubCommand extends BaseCommand implements Callable<Inte
     @Parameters(arity = "1..*")
     private Map<String, String> parameters;
 
-    @Option(names = {"--profile", "-p"}, description = "Set property in specified profile.")
+    @Option(names = {"--profile", "-p"}, description = "Set property in specified profile")
     private String profileName;
 
     @Inject
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommand.java
index bd7dcd7500..8c723dc8fe 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/CliConfigSubCommand.java
@@ -23,9 +23,9 @@ import java.util.concurrent.Callable;
 import org.apache.ignite.cli.call.cliconfig.CliConfigCall;
 import org.apache.ignite.cli.commands.BaseCommand;
 import org.apache.ignite.cli.commands.cliconfig.profile.CliConfigProfileCommand;
-import org.apache.ignite.cli.commands.decorators.ProfileDecorator;
 import org.apache.ignite.cli.core.call.CallExecutionPipeline;
 import org.apache.ignite.cli.core.call.StringCallInput;
+import org.apache.ignite.cli.decorators.ProfileDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Option;
 
@@ -39,7 +39,6 @@ import picocli.CommandLine.Option;
 })
 @Singleton
 public class CliConfigSubCommand extends BaseCommand implements Callable<Integer> {
-
     @Option(names = {"--profile", "-p"})
     private String profileName;
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigCreateProfileCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigCreateProfileCommand.java
index 848e9cfc13..e41ffa90c9 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigCreateProfileCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigCreateProfileCommand.java
@@ -29,15 +29,15 @@ import picocli.CommandLine.Option;
 /**
  * Command for create CLI profile.
  */
-@Command(name = "create", description = "Create profile command.")
+@Command(name = "create", description = "Create profile command")
 public class CliConfigCreateProfileCommand extends BaseCommand implements Callable<Integer> {
-    @Option(names = {"--name", "-n"}, required = true, description = "Name of new profile.")
+    @Option(names = {"--name", "-n"}, required = true, description = "Name of new profile")
     private String profileName;
 
-    @Option(names = {"--copy-from", "-c"}, description = "Profile whose content will be copied to new one.")
+    @Option(names = {"--copy-from", "-c"}, description = "Profile whose content will be copied to new one")
     private String copyFrom;
 
-    @Option(names = {"--activate", "-a"}, description = "Activate new profile as current or not.")
+    @Option(names = {"--activate", "-a"}, description = "Activate new profile as current or not")
     private boolean activate;
 
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigProfileCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigProfileCommand.java
index dd83cd92ec..9ea13f82af 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigProfileCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigProfileCommand.java
@@ -30,13 +30,13 @@ import picocli.CommandLine.Option;
  * Root profile command.
  */
 @Command(name = "profile",
-        description = "Create profile command.",
+        description = "Create profile command",
         subcommands = {
                 CliConfigCreateProfileCommand.class,
                 CliConfigShowProfileCommand.class
         })
 public class CliConfigProfileCommand extends BaseCommand implements Callable<Integer> {
-    @Option(names = {"--set-current", "-s"}, description = "Name of profile which should be activated as default.")
+    @Option(names = {"--set-current", "-s"}, description = "Name of profile which should be activated as default")
     private String profileName;
 
     @Inject
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigShowProfileCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigShowProfileCommand.java
index ca300b7e33..ae5b3b49a5 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigShowProfileCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cliconfig/profile/CliConfigShowProfileCommand.java
@@ -28,9 +28,8 @@ import picocli.CommandLine;
 /**
  * Show current profile command.
  */
-@CommandLine.Command(name = "show", description = "Show current default profile.")
+@CommandLine.Command(name = "show", description = "Show current default profile")
 public class CliConfigShowProfileCommand extends BaseCommand implements Callable<Integer> {
-
     @Inject
     private CliConfigShowProfileCall call;
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/ClusterCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/ClusterCommand.java
index fb73a8be44..afee472c91 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/ClusterCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/ClusterCommand.java
@@ -32,6 +32,6 @@ import picocli.CommandLine.Command;
                 ClusterInitSubCommand.class,
                 ClusterStatusSubCommand.class,
                 TopologyCommand.class},
-        description = "Manages an Ignite cluster.")
+        description = "Manages an Ignite cluster")
 public class ClusterCommand {
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/ClusterReplCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/ClusterReplCommand.java
index 44e517bb7b..78482ac30e 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/ClusterReplCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/ClusterReplCommand.java
@@ -33,6 +33,6 @@ import picocli.CommandLine.Command;
                 ClusterStatusReplSubCommand.class,
                 TopologyReplCommand.class
         },
-        description = "Manages an Ignite cluster.")
+        description = "Manages an Ignite cluster")
 public class ClusterReplCommand {
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigReplSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigReplSubCommand.java
index ecb6c6a94c..d9d3be021b 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigReplSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigReplSubCommand.java
@@ -24,7 +24,7 @@ import picocli.CommandLine.Command;
  */
 @Command(name = "config",
         subcommands = {ClusterConfigShowReplSubCommand.class, ClusterConfigUpdateReplSubCommand.class},
-        description = "Cluster config operations.")
+        description = "Cluster config operations")
 public class ClusterConfigReplSubCommand {
 
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigShowReplSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigShowReplSubCommand.java
index 8ec239b9ba..e01e670d13 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigShowReplSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigShowReplSubCommand.java
@@ -26,12 +26,9 @@ import org.apache.ignite.cli.call.configuration.ClusterConfigShowCall;
 import org.apache.ignite.cli.call.configuration.ClusterConfigShowCallInput;
 import org.apache.ignite.cli.commands.BaseCommand;
 import org.apache.ignite.cli.commands.questions.ConnectToClusterQuestion;
-import org.apache.ignite.cli.core.exception.ExceptionWriter;
-import org.apache.ignite.cli.core.exception.IgniteCliApiException;
-import org.apache.ignite.cli.core.exception.handler.IgniteCliApiExceptionHandler;
+import org.apache.ignite.cli.core.exception.handler.ShowConfigExceptionHandler;
 import org.apache.ignite.cli.core.flow.Flowable;
 import org.apache.ignite.cli.core.flow.builder.Flows;
-import org.apache.ignite.rest.client.invoker.ApiException;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Option;
 import picocli.CommandLine.Parameters;
@@ -40,18 +37,13 @@ import picocli.CommandLine.Parameters;
  * Command that shows configuration from the cluster in REPL mode.
  */
 @Command(name = "show",
-        description = "Shows cluster configuration.")
+        description = "Shows cluster configuration")
 public class ClusterConfigShowReplSubCommand extends BaseCommand implements Runnable {
-
-    /**
-     * Configuration selector option.
-     */
+    /** Configuration selector option. */
     @Parameters(arity = "0..1", description = "Configuration path selector")
     private String selector;
 
-    /**
-     * Cluster endpoint URL option.
-     */
+    /** Cluster endpoint URL option. */
     @Option(names = {CLUSTER_URL_OPTION}, description = CLUSTER_URL_DESC, descriptionKey = CLUSTER_URL_KEY)
     private String clusterUrl;
 
@@ -64,10 +56,10 @@ public class ClusterConfigShowReplSubCommand extends BaseCommand implements Runn
     @Override
     public void run() {
         question.askQuestionIfNotConnected(clusterUrl)
+                .exceptionHandler(new ShowConfigExceptionHandler())
                 .map(this::configShowCallInput)
                 .then(Flows.fromCall(call))
                 .toOutput(spec.commandLine().getOut(), spec.commandLine().getErr())
-                .exceptionHandler(new ShowConfigReplExceptionHandler())
                 .build()
                 .start(Flowable.empty());
     }
@@ -75,19 +67,4 @@ public class ClusterConfigShowReplSubCommand extends BaseCommand implements Runn
     private ClusterConfigShowCallInput configShowCallInput(String clusterUrl) {
         return ClusterConfigShowCallInput.builder().selector(selector).clusterUrl(clusterUrl).build();
     }
-
-    private static class ShowConfigReplExceptionHandler extends IgniteCliApiExceptionHandler {
-        @Override
-        public int handle(ExceptionWriter err, IgniteCliApiException e) {
-            if (e.getCause() instanceof ApiException) {
-                ApiException apiException = (ApiException) e.getCause();
-                if (apiException.getCode() == 500) { //TODO: https://issues.apache.org/jira/browse/IGNITE-17091
-                    err.write("Cannot show cluster config, probably you have not initialized the cluster. "
-                            + "Try to run 'cluster init' command.");
-                    return 1;
-                }
-            }
-            return super.handle(err, e);
-        }
-    }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigShowSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigShowSubCommand.java
index 2dda323076..12593cfbf9 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigShowSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigShowSubCommand.java
@@ -26,8 +26,9 @@ import java.util.concurrent.Callable;
 import org.apache.ignite.cli.call.configuration.ClusterConfigShowCall;
 import org.apache.ignite.cli.call.configuration.ClusterConfigShowCallInput;
 import org.apache.ignite.cli.commands.BaseCommand;
-import org.apache.ignite.cli.commands.decorators.JsonDecorator;
 import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.core.exception.handler.ShowConfigExceptionHandler;
+import org.apache.ignite.cli.decorators.JsonDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Option;
 import picocli.CommandLine.Parameters;
@@ -36,12 +37,9 @@ import picocli.CommandLine.Parameters;
  * Command that shows configuration from the cluster.
  */
 @Command(name = "show",
-        description = "Shows cluster configuration.")
+        description = "Shows cluster configuration")
 public class ClusterConfigShowSubCommand extends BaseCommand implements Callable<Integer> {
-
-    /**
-     * Configuration selector option.
-     */
+    /** Configuration selector option. */
     @Parameters(arity = "0..1", description = "Configuration path selector")
     private String selector;
 
@@ -62,6 +60,7 @@ public class ClusterConfigShowSubCommand extends BaseCommand implements Callable
                 .output(spec.commandLine().getOut())
                 .errOutput(spec.commandLine().getErr())
                 .decorator(new JsonDecorator())
+                .exceptionHandler(new ShowConfigExceptionHandler())
                 .build()
                 .runPipeline();
     }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigSubCommand.java
index 8d8eb57be5..7449889cc9 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigSubCommand.java
@@ -24,7 +24,6 @@ import picocli.CommandLine.Command;
  */
 @Command(name = "config",
         subcommands = {ClusterConfigShowSubCommand.class, ClusterConfigUpdateSubCommand.class},
-        description = "Cluster config operations.")
+        description = "Cluster config operations")
 public class ClusterConfigSubCommand {
-
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigUpdateReplSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigUpdateReplSubCommand.java
index 5e8c759200..90eadb9daa 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigUpdateReplSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigUpdateReplSubCommand.java
@@ -37,18 +37,14 @@ import picocli.CommandLine.Parameters;
  * Command that updates cluster configuration in REPL mode.
  */
 @Command(name = "update",
-        description = "Updates cluster configuration.")
+        description = "Updates cluster configuration")
 @Singleton
 public class ClusterConfigUpdateReplSubCommand extends BaseCommand implements Runnable {
-    /**
-     * Cluster endpoint URL option.
-     */
+    /** Cluster endpoint URL option. */
     @Option(names = {CLUSTER_URL_OPTION}, description = CLUSTER_URL_DESC, descriptionKey = CLUSTER_URL_KEY)
     private String clusterUrl;
 
-    /**
-     * Configuration that will be updated.
-     */
+    /** Configuration that will be updated. */
     @Parameters(index = "0")
     private String config;
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigUpdateSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigUpdateSubCommand.java
index b5d337b3a1..1852ccd1af 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigUpdateSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/config/ClusterConfigUpdateSubCommand.java
@@ -36,21 +36,17 @@ import picocli.CommandLine.Parameters;
  * Command that updates cluster configuration.
  */
 @Command(name = "update",
-        description = "Updates cluster configuration.")
+        description = "Updates cluster configuration")
 @Singleton
 public class ClusterConfigUpdateSubCommand extends BaseCommand implements Callable<Integer> {
     @Inject
     ClusterConfigUpdateCall call;
 
-    /**
-     * Cluster endpoint URL option.
-     */
+    /** Cluster endpoint URL option. */
     @Option(names = {CLUSTER_URL_OPTION}, description = CLUSTER_URL_DESC, descriptionKey = CLUSTER_URL_KEY)
     private String clusterUrl;
 
-    /**
-     * Configuration that will be updated.
-     */
+    /** Configuration that will be updated. */
     @Parameters(index = "0")
     private String config;
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/init/ClusterInitReplSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/init/ClusterInitReplSubCommand.java
index 61f8ea774c..70f923b2e3 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/init/ClusterInitReplSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/init/ClusterInitReplSubCommand.java
@@ -20,6 +20,7 @@ package org.apache.ignite.cli.commands.cluster.init;
 import static org.apache.ignite.cli.commands.OptionsConstants.CLUSTER_URL_DESC;
 import static org.apache.ignite.cli.commands.OptionsConstants.CLUSTER_URL_KEY;
 import static org.apache.ignite.cli.commands.OptionsConstants.CLUSTER_URL_OPTION;
+import static org.apache.ignite.cli.core.style.component.CommonMessages.CONNECT_OR_USE_CLUSTER_URL_MESSAGE;
 import static picocli.CommandLine.Command;
 
 import jakarta.inject.Inject;
@@ -36,12 +37,9 @@ import picocli.CommandLine.Option;
 /**
  * Initializes an Ignite cluster.
  */
-@Command(name = "init", description = "Initializes an Ignite cluster.")
+@Command(name = "init", description = "Initializes an Ignite cluster")
 public class ClusterInitReplSubCommand extends BaseCommand implements Runnable {
-
-    /**
-     * Cluster endpoint URL option.
-     */
+    /** Cluster endpoint URL option. */
     @Option(
             names = {CLUSTER_URL_OPTION}, description = CLUSTER_URL_DESC, descriptionKey = CLUSTER_URL_KEY,
             defaultValue = "http://localhost:10300"
@@ -89,8 +87,7 @@ public class ClusterInitReplSubCommand extends BaseCommand implements Runnable {
         } else if (clusterUrl != null) {
             input.clusterUrl(clusterUrl);
         } else {
-            spec.commandLine().getErr().println("You are not connected to node. Run 'connect' command or use '"
-                    + CLUSTER_URL_OPTION + "' option.");
+            spec.commandLine().getErr().println(CONNECT_OR_USE_CLUSTER_URL_MESSAGE.render());
             return;
         }
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/init/ClusterInitSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/init/ClusterInitSubCommand.java
index dbe2d66301..4f6998c954 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/init/ClusterInitSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/init/ClusterInitSubCommand.java
@@ -35,12 +35,9 @@ import picocli.CommandLine.Option;
 /**
  * Initializes an Ignite cluster.
  */
-@Command(name = "init", description = "Initializes an Ignite cluster.")
+@Command(name = "init", description = "Initializes an Ignite cluster")
 public class ClusterInitSubCommand extends BaseCommand implements Callable<Integer> {
-
-    /**
-     * Cluster endpoint URL option.
-     */
+    /** Cluster endpoint URL option. */
     @Option(
             names = {CLUSTER_URL_OPTION}, description = CLUSTER_URL_DESC, descriptionKey = CLUSTER_URL_KEY,
             defaultValue = "http://localhost:10300"
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/status/ClusterStatusReplSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/status/ClusterStatusReplSubCommand.java
index 9f0166cc39..216c0d8dad 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/status/ClusterStatusReplSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/status/ClusterStatusReplSubCommand.java
@@ -20,28 +20,26 @@ package org.apache.ignite.cli.commands.cluster.status;
 import static org.apache.ignite.cli.commands.OptionsConstants.CLUSTER_URL_DESC;
 import static org.apache.ignite.cli.commands.OptionsConstants.CLUSTER_URL_KEY;
 import static org.apache.ignite.cli.commands.OptionsConstants.CLUSTER_URL_OPTION;
+import static org.apache.ignite.cli.core.style.component.CommonMessages.CONNECT_OR_USE_CLUSTER_URL_MESSAGE;
 
 import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
 import org.apache.ignite.cli.call.cluster.status.ClusterStatusCall;
 import org.apache.ignite.cli.commands.BaseCommand;
-import org.apache.ignite.cli.commands.decorators.ClusterStatusDecorator;
 import org.apache.ignite.cli.core.call.CallExecutionPipeline;
 import org.apache.ignite.cli.core.call.StatusCallInput;
 import org.apache.ignite.cli.core.repl.Session;
+import org.apache.ignite.cli.decorators.ClusterStatusDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Option;
 
 /**
  * Command that prints status of ignite cluster.
  */
-@Command(name = "status", description = "Prints status of the cluster.")
+@Command(name = "status", description = "Prints status of the cluster")
 @Singleton
 public class ClusterStatusReplSubCommand extends BaseCommand implements Runnable {
-
-    /**
-     * Cluster endpoint URL option.
-     */
+    /** Cluster endpoint URL option. */
     @SuppressWarnings("PMD.UnusedPrivateField")
     @Option(names = {CLUSTER_URL_OPTION}, description = CLUSTER_URL_DESC, descriptionKey = CLUSTER_URL_KEY)
     private String clusterUrl;
@@ -62,8 +60,7 @@ public class ClusterStatusReplSubCommand extends BaseCommand implements Runnable
         } else if (session.isConnectedToNode()) {
             inputUrl = session.nodeUrl();
         } else {
-            spec.commandLine().getErr().println("You are not connected to node. Run 'connect' command or use '"
-                    + CLUSTER_URL_OPTION + "' option.");
+            spec.commandLine().getErr().println(CONNECT_OR_USE_CLUSTER_URL_MESSAGE.render());
             return;
         }
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/status/ClusterStatusSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/status/ClusterStatusSubCommand.java
index 7b5a7ac9e2..251e9335aa 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/status/ClusterStatusSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/cluster/status/ClusterStatusSubCommand.java
@@ -26,9 +26,9 @@ import jakarta.inject.Singleton;
 import java.util.concurrent.Callable;
 import org.apache.ignite.cli.call.cluster.status.ClusterStatusCall;
 import org.apache.ignite.cli.commands.BaseCommand;
-import org.apache.ignite.cli.commands.decorators.ClusterStatusDecorator;
 import org.apache.ignite.cli.core.call.CallExecutionPipeline;
 import org.apache.ignite.cli.core.call.StatusCallInput;
+import org.apache.ignite.cli.decorators.ClusterStatusDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Option;
 
@@ -37,13 +37,10 @@ import picocli.CommandLine.Option;
  */
 @Command(name = "status",
         aliases = "cluster show", //TODO: https://issues.apache.org/jira/browse/IGNITE-17102
-        description = "Prints status of the cluster.")
+        description = "Prints status of the cluster")
 @Singleton
 public class ClusterStatusSubCommand extends BaseCommand implements Callable<Integer> {
-
-    /**
-     * Cluster endpoint URL option.
-     */
+    /** Cluster endpoint URL option. */
     @SuppressWarnings("PMD.UnusedPrivateField")
     @Option(names = {CLUSTER_URL_OPTION}, description = CLUSTER_URL_DESC, descriptionKey = CLUSTER_URL_KEY)
     private String clusterUrl;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/ConnectCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/ConnectCommand.java
index 77c258ef56..673fb5c52d 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/ConnectCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/ConnectCommand.java
@@ -30,15 +30,12 @@ import picocli.CommandLine.Parameters;
 /**
  * Connects to the Ignite 3 node.
  */
-@Command(name = "connect", description = "Connect to Ignite 3 node.")
+@Command(name = "connect", description = "Connect to Ignite 3 node")
 @Singleton
 public class ConnectCommand extends BaseCommand implements Runnable {
-
-    /**
-     * Cluster url option.
-     */
+    /** Cluster url option. */
     @Parameters(
-            description = "Ignite node url.",
+            description = "Ignite node url",
             descriptionKey = ConfigConstants.CLUSTER_URL
     )
     private String nodeUrl;
@@ -56,5 +53,4 @@ public class ConnectCommand extends BaseCommand implements Runnable {
                 .build()
                 .runPipeline();
     }
-
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/DisconnectCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/DisconnectCommand.java
index a6da5d1a44..d3ebd5fed7 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/DisconnectCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/connect/DisconnectCommand.java
@@ -28,7 +28,7 @@ import picocli.CommandLine.Command;
 /**
  * Connects to the Ignite 3 node.
  */
-@Command(name = "disconnect", description = "Disconnect from Ignite 3 node.")
+@Command(name = "disconnect", description = "Disconnect from Ignite 3 node")
 @Singleton
 public class DisconnectCommand extends BaseCommand implements Runnable {
     @Inject
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/NodeCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/NodeCommand.java
index a00703c831..f96daa30f3 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/NodeCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/NodeCommand.java
@@ -28,9 +28,8 @@ import picocli.CommandLine.Mixin;
  */
 @Command(name = "node",
         subcommands = {NodeConfigSubCommand.class, NodeStatusSubCommand.class},
-        description = "Node operations.")
+        description = "Node operations")
 public class NodeCommand {
-
     @Mixin
     NodeCommandSpec nodeCommandSpec;
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/NodeReplCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/NodeReplCommand.java
index 04f7edce8a..9cf11b0bd4 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/NodeReplCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/NodeReplCommand.java
@@ -28,7 +28,7 @@ import picocli.CommandLine.Mixin;
  */
 @Command(name = "node",
         subcommands = {NodeConfigReplSubCommand.class, NodeStatusReplSubCommand.class},
-        description = "Node operations.")
+        description = "Node operations")
 public class NodeReplCommand {
     @Mixin
     NodeCommandSpec nodeCommandSpec;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/config/NodeConfigReplSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/config/NodeConfigReplSubCommand.java
index 712f8367dc..1674e45619 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/config/NodeConfigReplSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/config/NodeConfigReplSubCommand.java
@@ -24,7 +24,6 @@ import picocli.CommandLine.Command;
  */
 @Command(name = "config",
         subcommands = {NodeConfigShowReplSubCommand.class, NodeConfigUpdateReplSubCommand.class},
-        description = "Node config operations.")
+        description = "Node config operations")
 public class NodeConfigReplSubCommand {
-
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/config/NodeConfigShowSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/config/NodeConfigShowSubCommand.java
index f92a853987..3da2b22346 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/config/NodeConfigShowSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/config/NodeConfigShowSubCommand.java
@@ -26,8 +26,8 @@ import java.util.concurrent.Callable;
 import org.apache.ignite.cli.call.configuration.NodeConfigShowCall;
 import org.apache.ignite.cli.call.configuration.NodeConfigShowCallInput;
 import org.apache.ignite.cli.commands.BaseCommand;
-import org.apache.ignite.cli.commands.decorators.JsonDecorator;
 import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.decorators.JsonDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Option;
 import picocli.CommandLine.Parameters;
@@ -36,18 +36,13 @@ import picocli.CommandLine.Parameters;
  * Command that shows configuration from the cluster.
  */
 @Command(name = "show",
-        description = "Shows node configuration.")
+        description = "Shows node configuration")
 public class NodeConfigShowSubCommand extends BaseCommand implements Callable<Integer> {
-
-    /**
-     * Configuration selector option.
-     */
+    /** Configuration selector option. */
     @Parameters(arity = "0..1", description = "Configuration path selector")
     private String selector;
 
-    /**
-     * Node URL option.
-     */
+    /** Node URL option. */
     @Option(names = {NODE_URL_OPTION}, description = NODE_URL_DESC, descriptionKey = CLUSTER_URL_KEY)
     private String nodeUrl;
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/config/NodeConfigSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/config/NodeConfigSubCommand.java
index 4bbce2cf95..43ef7c6c39 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/config/NodeConfigSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/config/NodeConfigSubCommand.java
@@ -24,7 +24,6 @@ import picocli.CommandLine.Command;
  */
 @Command(name = "config",
         subcommands = {NodeConfigShowSubCommand.class, NodeConfigUpdateSubCommand.class},
-        description = "Node config operations.")
+        description = "Node config operations")
 public class NodeConfigSubCommand {
-
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/config/NodeConfigUpdateSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/config/NodeConfigUpdateSubCommand.java
index f2f8006b09..b682f0eddc 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/config/NodeConfigUpdateSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/config/NodeConfigUpdateSubCommand.java
@@ -36,18 +36,14 @@ import picocli.CommandLine.Parameters;
  * Command that updates node configuration.
  */
 @Command(name = "update",
-        description = "Updates node configuration.")
+        description = "Updates node configuration")
 @Singleton
 public class NodeConfigUpdateSubCommand extends BaseCommand implements Callable<Integer> {
-    /**
-     * Node URL option.
-     */
+    /** Node URL option. */
     @Option(names = {NODE_URL_OPTION}, description = NODE_URL_DESC, descriptionKey = CLUSTER_URL_KEY)
     private String nodeUrl;
 
-    /**
-     * Configuration that will be updated.
-     */
+    /** Configuration that will be updated. */
     @Parameters(index = "0")
     private String config;
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusReplSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusReplSubCommand.java
index 43672b05fc..dc63667f0a 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusReplSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusReplSubCommand.java
@@ -20,16 +20,17 @@ package org.apache.ignite.cli.commands.node.status;
 import static org.apache.ignite.cli.commands.OptionsConstants.CLUSTER_URL_KEY;
 import static org.apache.ignite.cli.commands.OptionsConstants.NODE_URL_DESC;
 import static org.apache.ignite.cli.commands.OptionsConstants.NODE_URL_OPTION;
+import static org.apache.ignite.cli.core.style.component.CommonMessages.CONNECT_OR_USE_NODE_URL_MESSAGE;
 
 import jakarta.inject.Inject;
 import jakarta.inject.Singleton;
 import java.util.concurrent.Callable;
 import org.apache.ignite.cli.call.node.status.NodeStatusCall;
 import org.apache.ignite.cli.commands.BaseCommand;
-import org.apache.ignite.cli.commands.decorators.NodeStatusDecorator;
 import org.apache.ignite.cli.core.call.CallExecutionPipeline;
 import org.apache.ignite.cli.core.call.StatusCallInput;
 import org.apache.ignite.cli.core.repl.Session;
+import org.apache.ignite.cli.decorators.NodeStatusDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Option;
 
@@ -37,13 +38,10 @@ import picocli.CommandLine.Option;
  * Display the node status in REPL.
  */
 @Command(name = "status",
-        description = "Prints status of the node.")
+        description = "Prints status of the node")
 @Singleton
 public class NodeStatusReplSubCommand extends BaseCommand implements Callable<Integer> {
-
-    /**
-     * Node URL option.
-     */
+    /** Node URL option. */
     @SuppressWarnings("PMD.UnusedPrivateField")
     @Option(names = {NODE_URL_OPTION}, description = NODE_URL_DESC, descriptionKey = CLUSTER_URL_KEY)
     private String nodeUrl;
@@ -64,8 +62,7 @@ public class NodeStatusReplSubCommand extends BaseCommand implements Callable<In
         } else if (session.isConnectedToNode()) {
             inputUrl = session.nodeUrl();
         } else {
-            spec.commandLine().getErr().println("You are not connected to node. Run 'connect' command or use '"
-                    + NODE_URL_OPTION + "' option.");
+            spec.commandLine().getErr().println(CONNECT_OR_USE_NODE_URL_MESSAGE.render());
             return 2;
         }
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusSubCommand.java
index 6a42402795..cb68811895 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/node/status/NodeStatusSubCommand.java
@@ -26,9 +26,9 @@ import jakarta.inject.Singleton;
 import java.util.concurrent.Callable;
 import org.apache.ignite.cli.call.node.status.NodeStatusCall;
 import org.apache.ignite.cli.commands.BaseCommand;
-import org.apache.ignite.cli.commands.decorators.NodeStatusDecorator;
 import org.apache.ignite.cli.core.call.CallExecutionPipeline;
 import org.apache.ignite.cli.core.call.StatusCallInput;
+import org.apache.ignite.cli.decorators.NodeStatusDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Option;
 
@@ -36,13 +36,10 @@ import picocli.CommandLine.Option;
  * Display the node status.
  */
 @Command(name = "status",
-        description = "Prints status of the node.")
+        description = "Prints status of the node")
 @Singleton
 public class NodeStatusSubCommand extends BaseCommand implements Callable<Integer> {
-
-    /**
-     * Node URL option.
-     */
+    /** Node URL option. */
     @SuppressWarnings("PMD.UnusedPrivateField")
     @Option(names = {NODE_URL_OPTION}, description = NODE_URL_DESC, descriptionKey = CLUSTER_URL_KEY)
     private String nodeUrl;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/questions/ConnectToClusterQuestion.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/questions/ConnectToClusterQuestion.java
index 5c3a6496e2..9dc1f5df1f 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/questions/ConnectToClusterQuestion.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/questions/ConnectToClusterQuestion.java
@@ -30,6 +30,8 @@ import org.apache.ignite.cli.core.flow.builder.FlowBuilder;
 import org.apache.ignite.cli.core.flow.builder.Flows;
 import org.apache.ignite.cli.core.repl.Session;
 import org.apache.ignite.cli.core.repl.context.CommandLineContextProvider;
+import org.apache.ignite.cli.core.style.component.QuestionUiComponent;
+import org.apache.ignite.cli.core.style.element.UiElements;
 
 
 /**
@@ -37,7 +39,6 @@ import org.apache.ignite.cli.core.repl.context.CommandLineContextProvider;
  */
 @Singleton
 public class ConnectToClusterQuestion {
-
     @Inject
     private ConnectCall connectCall;
 
@@ -59,11 +60,14 @@ public class ConnectToClusterQuestion {
      */
     public FlowBuilder<Void, String> askQuestionIfNotConnected(String clusterUrl) {
         String defaultUrl = configManagerProvider.get().getCurrentProperty(ConfigConstants.CLUSTER_URL);
-        String question = "You are not connected to node. Do you want to connect to the default node "
-                + defaultUrl + " ? [Y/n] ";
+
+        QuestionUiComponent questionUiComponent = QuestionUiComponent.fromQuestion(
+                "You are not connected to node. Do you want to connect to the default node %s? %s ",
+                UiElements.url(defaultUrl), UiElements.yesNo()
+        );
 
         return Flows.from(clusterUrlOrSessionNode(clusterUrl))
-                .ifThen(Objects::isNull, Flows.<String, ConnectCallInput>acceptQuestion(question,
+                .ifThen(Objects::isNull, Flows.<String, ConnectCallInput>acceptQuestion(questionUiComponent,
                                 () -> new ConnectCallInput(defaultUrl))
                         .then(Flows.fromCall(connectCall))
                         .toOutput(CommandLineContextProvider.getContext())
@@ -81,13 +85,17 @@ public class ConnectToClusterQuestion {
     public void askQuestionOnReplStart() {
         String defaultUrl = configManagerProvider.get().getCurrentProperty(ConfigConstants.CLUSTER_URL);
         String lastConnectedUrl = stateConfigProvider.get().getProperty(ConfigConstants.LAST_CONNECTED_URL);
-        String question;
+        QuestionUiComponent question;
         String clusterUrl;
         if (lastConnectedUrl != null) {
-            question = "Do you want to connect to the last connected node " + lastConnectedUrl + " ? [Y/n]";
+            question = QuestionUiComponent.fromQuestion(
+                    "Do you want to connect to the last connected node %s? %s ", UiElements.url(lastConnectedUrl), UiElements.yesNo()
+            );
             clusterUrl = lastConnectedUrl;
         } else {
-            question = "Do you want to connect to the default node " + defaultUrl + " ? [Y/n]";
+            question = QuestionUiComponent.fromQuestion(
+                    "Do you want to connect to the default node %s? %s ", UiElements.url(defaultUrl), UiElements.yesNo()
+            );
             clusterUrl = defaultUrl;
         }
 
@@ -100,8 +108,9 @@ public class ConnectToClusterQuestion {
     }
 
     private FlowBuilder<String, String> defaultUrlQuestion(String lastConnectedUrl) {
-        return Flows.acceptQuestion("Would you like to use " + lastConnectedUrl + " as the default URL? [Y/n]",
-                () -> {
+        return Flows.acceptQuestion(QuestionUiComponent.fromQuestion(
+                "Would you like to use %s as the default URL? %s ", UiElements.url(lastConnectedUrl), UiElements.yesNo()
+                ), () -> {
                     configManagerProvider.get().setProperty(ConfigConstants.CLUSTER_URL, lastConnectedUrl);
                     return "Config saved";
                 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlCommand.java
index bd968e93a8..c16142c235 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlCommand.java
@@ -25,11 +25,11 @@ import java.sql.SQLException;
 import java.util.concurrent.Callable;
 import org.apache.ignite.cli.call.sql.SqlQueryCall;
 import org.apache.ignite.cli.commands.BaseCommand;
-import org.apache.ignite.cli.commands.decorators.SqlQueryResultDecorator;
 import org.apache.ignite.cli.core.call.CallExecutionPipeline;
 import org.apache.ignite.cli.core.call.StringCallInput;
 import org.apache.ignite.cli.core.exception.ExceptionWriter;
 import org.apache.ignite.cli.core.exception.handler.SqlExceptionHandler;
+import org.apache.ignite.cli.decorators.SqlQueryResultDecorator;
 import org.apache.ignite.cli.deprecated.IgniteCliException;
 import org.apache.ignite.cli.sql.SqlManager;
 import picocli.CommandLine.ArgGroup;
@@ -40,9 +40,8 @@ import picocli.CommandLine.Parameters;
 /**
  * Command for sql execution.
  */
-@Command(name = "sql", description = "Executes SQL query.")
+@Command(name = "sql", description = "Executes SQL query")
 public class SqlCommand extends BaseCommand implements Callable<Integer> {
-
     @Option(names = {"-u", "--jdbc-url"}, required = true,
             descriptionKey = "ignite.jdbc-url", description = "JDBC url to ignite cluster")
     private String jdbc;
@@ -51,10 +50,10 @@ public class SqlCommand extends BaseCommand implements Callable<Integer> {
     private ExecOptions execOptions;
 
     private static class ExecOptions {
-        @Parameters(index = "0", description = "SQL query to execute.")
+        @Parameters(index = "0", description = "SQL query to execute")
         private String command;
 
-        @Option(names = {"-f", "--script-file"}, description = "Path to file with SQL commands to execute.")
+        @Option(names = {"-f", "--script-file"}, description = "Path to file with SQL commands to execute")
         private File file;
     }
 
@@ -62,7 +61,7 @@ public class SqlCommand extends BaseCommand implements Callable<Integer> {
         try {
             return String.join("\n", Files.readAllLines(file.toPath(), StandardCharsets.UTF_8));
         } catch (IOException e) {
-            throw new IgniteCliException("File with command not found.");
+            throw new IgniteCliException("File with command not found");
         }
     }
 
@@ -83,5 +82,4 @@ public class SqlCommand extends BaseCommand implements Callable<Integer> {
             return new SqlExceptionHandler().handle(ExceptionWriter.fromPrintWriter(spec.commandLine().getErr()), e);
         }
     }
-
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlReplCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlReplCommand.java
index 1647916c82..fe4c820b5e 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlReplCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlReplCommand.java
@@ -25,7 +25,6 @@ import java.nio.file.Files;
 import java.sql.SQLException;
 import org.apache.ignite.cli.call.sql.SqlQueryCall;
 import org.apache.ignite.cli.commands.BaseCommand;
-import org.apache.ignite.cli.commands.decorators.SqlQueryResultDecorator;
 import org.apache.ignite.cli.core.CallExecutionPipelineProvider;
 import org.apache.ignite.cli.core.call.CallExecutionPipeline;
 import org.apache.ignite.cli.core.call.StringCallInput;
@@ -35,6 +34,7 @@ import org.apache.ignite.cli.core.exception.handler.SqlExceptionHandler;
 import org.apache.ignite.cli.core.repl.Repl;
 import org.apache.ignite.cli.core.repl.executor.RegistryCommandExecutor;
 import org.apache.ignite.cli.core.repl.executor.ReplExecutorProvider;
+import org.apache.ignite.cli.decorators.SqlQueryResultDecorator;
 import org.apache.ignite.cli.deprecated.IgniteCliException;
 import org.apache.ignite.cli.sql.SqlManager;
 import org.apache.ignite.cli.sql.SqlSchemaProvider;
@@ -46,7 +46,7 @@ import picocli.CommandLine.Parameters;
 /**
  * Command for sql execution in REPL mode.
  */
-@Command(name = "sql", description = "Executes SQL query.")
+@Command(name = "sql", description = "Executes SQL query")
 public class SqlReplCommand extends BaseCommand implements Runnable {
     @Option(names = {"-u", "--jdbc-url"}, required = true,
             descriptionKey = "ignite.jdbc-url", description = "JDBC url to ignite cluster")
@@ -56,10 +56,10 @@ public class SqlReplCommand extends BaseCommand implements Runnable {
     private ExecOptions execOptions;
 
     private static class ExecOptions {
-        @Parameters(index = "0", description = "SQL query to execute.")
+        @Parameters(index = "0", description = "SQL query to execute")
         private String command;
 
-        @Option(names = {"-f", "--script-file"}, description = "Path to file with SQL commands to execute.")
+        @Option(names = {"-f", "--script-file"}, description = "Path to file with SQL commands to execute")
         private File file;
     }
 
@@ -70,7 +70,7 @@ public class SqlReplCommand extends BaseCommand implements Runnable {
         try {
             return String.join("\n", Files.readAllLines(file.toPath(), StandardCharsets.UTF_8));
         } catch (IOException e) {
-            throw new IgniteCliException("File with command not found.");
+            throw new IgniteCliException("File with command not found");
         }
     }
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlReplTopLevelCliCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlReplTopLevelCliCommand.java
index 6613644107..0e977bb795 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlReplTopLevelCliCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/sql/SqlReplTopLevelCliCommand.java
@@ -26,7 +26,7 @@ import picocli.shell.jline3.PicocliCommands;
  */
 @CommandLine.Command(name = "",
         description = {""},
-        footer = {"", "Press Ctrl-D to exit."},
+        footer = {"", "Press Ctrl-D to exit"},
         subcommands = {
             CommandLine.HelpCommand.class,
             PicocliCommands.ClearScreen.class
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/LogicalTopologyReplSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/LogicalTopologyReplSubCommand.java
index e1902c6377..be32c99106 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/LogicalTopologyReplSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/LogicalTopologyReplSubCommand.java
@@ -20,6 +20,7 @@ package org.apache.ignite.cli.commands.topology;
 import static org.apache.ignite.cli.commands.OptionsConstants.CLUSTER_URL_DESC;
 import static org.apache.ignite.cli.commands.OptionsConstants.CLUSTER_URL_KEY;
 import static org.apache.ignite.cli.commands.OptionsConstants.CLUSTER_URL_OPTION;
+import static org.apache.ignite.cli.core.style.component.CommonMessages.CONNECT_OR_USE_NODE_URL_MESSAGE;
 
 import jakarta.inject.Inject;
 import java.util.concurrent.Callable;
@@ -27,9 +28,9 @@ import org.apache.ignite.cli.call.cluster.topology.LogicalTopologyCall;
 import org.apache.ignite.cli.call.cluster.topology.TopologyCallInput;
 import org.apache.ignite.cli.call.cluster.topology.TopologyCallInput.TopologyCallInputBuilder;
 import org.apache.ignite.cli.commands.BaseCommand;
-import org.apache.ignite.cli.commands.decorators.TopologyDecorator;
 import org.apache.ignite.cli.core.call.CallExecutionPipeline;
 import org.apache.ignite.cli.core.repl.Session;
+import org.apache.ignite.cli.decorators.TopologyDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Option;
 
@@ -38,9 +39,7 @@ import picocli.CommandLine.Option;
  */
 @Command(name = "logical")
 public class LogicalTopologyReplSubCommand extends BaseCommand implements Callable<Integer> {
-    /**
-     * Cluster endpoint URL option.
-     */
+    /** Cluster endpoint URL option. */
     @Option(names = {CLUSTER_URL_OPTION}, description = CLUSTER_URL_DESC, descriptionKey = CLUSTER_URL_KEY)
     private String clusterUrl;
 
@@ -57,11 +56,10 @@ public class LogicalTopologyReplSubCommand extends BaseCommand implements Callab
 
         if (clusterUrl != null) {
             inputBuilder.clusterUrl(clusterUrl);
-        } else if (session.isConnectedToNode())  {
+        } else if (session.isConnectedToNode()) {
             inputBuilder.clusterUrl(session.nodeUrl());
         } else {
-            spec.commandLine().getErr().println("You are not connected to node. Run 'connect' command or use '"
-                    + CLUSTER_URL_OPTION + "' option.");
+            spec.commandLine().getErr().println(CONNECT_OR_USE_NODE_URL_MESSAGE.render());
             return 2;
         }
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/LogicalTopologySubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/LogicalTopologySubCommand.java
index 27bca3d243..d7043e84ef 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/LogicalTopologySubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/LogicalTopologySubCommand.java
@@ -26,8 +26,8 @@ import java.util.concurrent.Callable;
 import org.apache.ignite.cli.call.cluster.topology.LogicalTopologyCall;
 import org.apache.ignite.cli.call.cluster.topology.TopologyCallInput;
 import org.apache.ignite.cli.commands.BaseCommand;
-import org.apache.ignite.cli.commands.decorators.TopologyDecorator;
 import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.decorators.TopologyDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Option;
 
@@ -36,9 +36,7 @@ import picocli.CommandLine.Option;
  */
 @Command(name = "logical")
 public class LogicalTopologySubCommand extends BaseCommand implements Callable<Integer> {
-    /**
-     * Cluster endpoint URL option.
-     */
+    /** Cluster endpoint URL option. */
     @Option(names = {CLUSTER_URL_OPTION}, description = CLUSTER_URL_DESC, descriptionKey = CLUSTER_URL_KEY)
     private String clusterUrl;
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/PhysicalTopologyReplSubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/PhysicalTopologyReplSubCommand.java
index 50a9517ee2..dfee7731f6 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/PhysicalTopologyReplSubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/PhysicalTopologyReplSubCommand.java
@@ -20,6 +20,7 @@ package org.apache.ignite.cli.commands.topology;
 import static org.apache.ignite.cli.commands.OptionsConstants.CLUSTER_URL_DESC;
 import static org.apache.ignite.cli.commands.OptionsConstants.CLUSTER_URL_KEY;
 import static org.apache.ignite.cli.commands.OptionsConstants.CLUSTER_URL_OPTION;
+import static org.apache.ignite.cli.core.style.component.CommonMessages.CONNECT_OR_USE_NODE_URL_MESSAGE;
 
 import jakarta.inject.Inject;
 import java.util.concurrent.Callable;
@@ -27,9 +28,9 @@ import org.apache.ignite.cli.call.cluster.topology.PhysicalTopologyCall;
 import org.apache.ignite.cli.call.cluster.topology.TopologyCallInput;
 import org.apache.ignite.cli.call.cluster.topology.TopologyCallInput.TopologyCallInputBuilder;
 import org.apache.ignite.cli.commands.BaseCommand;
-import org.apache.ignite.cli.commands.decorators.TopologyDecorator;
 import org.apache.ignite.cli.core.call.CallExecutionPipeline;
 import org.apache.ignite.cli.core.repl.Session;
+import org.apache.ignite.cli.decorators.TopologyDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Option;
 
@@ -38,9 +39,7 @@ import picocli.CommandLine.Option;
  */
 @Command(name = "physical")
 public class PhysicalTopologyReplSubCommand extends BaseCommand implements Callable<Integer> {
-    /**
-     * Cluster endpoint URL option.
-     */
+    /** Cluster endpoint URL option. */
     @Option(names = {CLUSTER_URL_OPTION}, description = CLUSTER_URL_DESC, descriptionKey = CLUSTER_URL_KEY)
     private String clusterUrl;
 
@@ -57,11 +56,10 @@ public class PhysicalTopologyReplSubCommand extends BaseCommand implements Calla
 
         if (clusterUrl != null) {
             inputBuilder.clusterUrl(clusterUrl);
-        } else if (session.isConnectedToNode())  {
+        } else if (session.isConnectedToNode()) {
             inputBuilder.clusterUrl(session.nodeUrl());
         } else {
-            spec.commandLine().getErr().println("You are not connected to node. Run 'connect' command or use '"
-                    + CLUSTER_URL_OPTION + "' option.");
+            spec.commandLine().getErr().println(CONNECT_OR_USE_NODE_URL_MESSAGE.render());
             return 2;
         }
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/PhysicalTopologySubCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/PhysicalTopologySubCommand.java
index 59ad74b967..10642df21b 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/PhysicalTopologySubCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/PhysicalTopologySubCommand.java
@@ -26,8 +26,8 @@ import java.util.concurrent.Callable;
 import org.apache.ignite.cli.call.cluster.topology.PhysicalTopologyCall;
 import org.apache.ignite.cli.call.cluster.topology.TopologyCallInput;
 import org.apache.ignite.cli.commands.BaseCommand;
-import org.apache.ignite.cli.commands.decorators.TopologyDecorator;
 import org.apache.ignite.cli.core.call.CallExecutionPipeline;
+import org.apache.ignite.cli.decorators.TopologyDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Option;
 
@@ -36,9 +36,7 @@ import picocli.CommandLine.Option;
  */
 @Command(name = "physical")
 public class PhysicalTopologySubCommand extends BaseCommand implements Callable<Integer> {
-    /**
-     * Cluster endpoint URL option.
-     */
+    /** Cluster endpoint URL option. */
     @Option(names = {CLUSTER_URL_OPTION}, description = CLUSTER_URL_DESC, descriptionKey = CLUSTER_URL_KEY)
     private String clusterUrl;
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyCommand.java
index 5da14720fa..c68da8af8f 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyCommand.java
@@ -18,21 +18,14 @@
 package org.apache.ignite.cli.commands.topology;
 
 import jakarta.inject.Singleton;
-import java.util.concurrent.Callable;
 import org.apache.ignite.cli.commands.BaseCommand;
 import picocli.CommandLine.Command;
 
 /**
  * Command that prints ignite cluster topology.
  */
-@Command(name = "topology", description = "Prints topology information.",
+@Command(name = "topology", description = "Prints topology information",
         subcommands = {PhysicalTopologySubCommand.class, LogicalTopologySubCommand.class })
 @Singleton
-public class TopologyCommand extends BaseCommand implements Callable<Integer> {
-
-    /** {@inheritDoc} */
-    @Override
-    public Integer call() {
-        return 0;
-    }
+public class TopologyCommand extends BaseCommand {
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyReplCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyReplCommand.java
index fa2c9ed054..7b7a1eaa98 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyReplCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/topology/TopologyReplCommand.java
@@ -18,21 +18,14 @@
 package org.apache.ignite.cli.commands.topology;
 
 import jakarta.inject.Singleton;
-import java.util.concurrent.Callable;
 import org.apache.ignite.cli.commands.BaseCommand;
 import picocli.CommandLine.Command;
 
 /**
  * Command that prints ignite cluster topology in REPL mode.
  */
-@Command(name = "topology", description = "Prints topology information.",
+@Command(name = "topology", description = "Prints topology information",
         subcommands = {PhysicalTopologyReplSubCommand.class, LogicalTopologyReplSubCommand.class })
 @Singleton
-public class TopologyReplCommand extends BaseCommand implements Callable<Integer> {
-
-    /** {@inheritDoc} */
-    @Override
-    public Integer call() {
-        return 0;
-    }
+public class TopologyReplCommand extends BaseCommand {
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/version/VersionCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/commands/version/VersionCommand.java
index 046d319e82..465209ea92 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/version/VersionCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/commands/version/VersionCommand.java
@@ -26,10 +26,9 @@ import picocli.CommandLine.Command;
 /**
  * Command that prints CLI version.
  */
-@Command(name = "version", description = "Prints CLI version.")
+@Command(name = "version", description = "Prints CLI version")
 @Singleton
 public class VersionCommand extends BaseCommand implements Runnable {
-
     @Inject
     private VersionProvider versionProvider;
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/SectionAlreadyExistsException.java b/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/SectionAlreadyExistsException.java
index 72b69ffe7e..84def57b7f 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/SectionAlreadyExistsException.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/SectionAlreadyExistsException.java
@@ -22,6 +22,6 @@ package org.apache.ignite.cli.config.ini;
  */
 public class SectionAlreadyExistsException extends RuntimeException {
     public SectionAlreadyExistsException(String name) {
-        super("Section " + name + " already exists.");
+        super("Section " + name + " already exists");
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/call/CallExecutionPipeline.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/CallExecutionPipeline.java
index 38c012ca8a..c9f43dd28f 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/core/call/CallExecutionPipeline.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/call/CallExecutionPipeline.java
@@ -21,13 +21,13 @@ import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.nio.charset.Charset;
 import java.util.function.Supplier;
-import org.apache.ignite.cli.commands.decorators.DefaultDecorator;
 import org.apache.ignite.cli.core.decorator.Decorator;
 import org.apache.ignite.cli.core.decorator.TerminalOutput;
 import org.apache.ignite.cli.core.exception.ExceptionHandler;
 import org.apache.ignite.cli.core.exception.ExceptionHandlers;
 import org.apache.ignite.cli.core.exception.ExceptionWriter;
 import org.apache.ignite.cli.core.exception.handler.DefaultExceptionHandlers;
+import org.apache.ignite.cli.decorators.DefaultDecorator;
 
 /**
  * Call execution pipeline.
@@ -36,34 +36,22 @@ import org.apache.ignite.cli.core.exception.handler.DefaultExceptionHandlers;
  * @param <T> Call output's body type.
  */
 public class CallExecutionPipeline<I extends CallInput, T> {
-    /**
-     * Call to execute.
-     */
+    /** Call to execute. */
     private final Call<I, T> call;
 
-    /**
-     * Writer for execution output.
-     */
+    /** Writer for execution output. */
     private final PrintWriter output;
 
-    /**
-     * Writer for error execution output.
-     */
+    /** Writer for error execution output. */
     private final PrintWriter errOutput;
 
-    /**
-     * Decorator that decorates call's output.
-     */
+    /** Decorator that decorates call's output. */
     private final Decorator<T, TerminalOutput> decorator;
 
-    /**
-     * Handlers for any exceptions.
-     */
+    /** Handlers for any exceptions. */
     private final ExceptionHandlers exceptionHandlers;
 
-    /**
-     * Provider for call's input.
-     */
+    /** Provider for call's input. */
     private final Supplier<I> inputProvider;
 
     private CallExecutionPipeline(Call<I, T> call,
@@ -85,8 +73,7 @@ public class CallExecutionPipeline<I extends CallInput, T> {
      *
      * @return builder for {@link CallExecutionPipeline}.
      */
-    public static <I extends CallInput, T> CallExecutionPipelineBuilder<I, T> builder(
-            Call<I, T> call) {
+    public static <I extends CallInput, T> CallExecutionPipelineBuilder<I, T> builder(Call<I, T> call) {
         return new CallExecutionPipelineBuilder<>(call);
     }
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/decorator/DecoratorRegistry.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/decorator/DecoratorRegistry.java
index 1b1162d0fb..ca22a3bcd9 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/core/decorator/DecoratorRegistry.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/decorator/DecoratorRegistry.java
@@ -19,7 +19,7 @@ package org.apache.ignite.cli.core.decorator;
 
 import java.util.HashMap;
 import java.util.Map;
-import org.apache.ignite.cli.commands.decorators.DefaultDecorator;
+import org.apache.ignite.cli.decorators.DefaultDecorator;
 
 /**
  * Registry for {@link Decorator}.
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/ExceptionHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/ExceptionHandler.java
index 84583ed5da..c65de68999 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/ExceptionHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/ExceptionHandler.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.cli.core.exception;
 
+import org.apache.ignite.cli.core.style.component.ErrorUiComponent;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 
@@ -32,7 +33,14 @@ public interface ExceptionHandler<T extends Throwable> {
         @Override
         public int handle(ExceptionWriter err, Throwable e) {
             LOG.error("Unhandled exception", e);
-            err.write("Internal error!");
+            err.write(
+                    ErrorUiComponent.builder()
+                            .header("Unknown error")
+                            .details(e.getMessage())
+                            .build()
+                            .render()
+            );
+
             return 1;
         }
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ConfigStoringExceptionHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ConfigStoringExceptionHandler.java
index 0f317483ca..534ab1eb64 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ConfigStoringExceptionHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ConfigStoringExceptionHandler.java
@@ -20,6 +20,7 @@ package org.apache.ignite.cli.core.exception.handler;
 import org.apache.ignite.cli.config.ConfigStoringException;
 import org.apache.ignite.cli.core.exception.ExceptionHandler;
 import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.cli.core.style.component.ErrorUiComponent;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 
@@ -31,8 +32,13 @@ public class ConfigStoringExceptionHandler implements ExceptionHandler<ConfigSto
 
     @Override
     public int handle(ExceptionWriter err, ConfigStoringException e) {
-        log.error("CLI config storing error: ", e);
-        err.write("Error happened while saving CLI config " + e.getMessage());
+        ErrorUiComponent errorComponent = ErrorUiComponent.builder()
+                .header("Could not save CLI config")
+                .details(e.getMessage())
+                .build();
+
+        log.error(errorComponent.header(), e);
+        err.write(errorComponent.render());
         return 1;
     }
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/IgniteCliApiExceptionHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/IgniteCliApiExceptionHandler.java
index 1ace50ea2d..53c92517c7 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/IgniteCliApiExceptionHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/IgniteCliApiExceptionHandler.java
@@ -17,14 +17,22 @@
 
 package org.apache.ignite.cli.core.exception.handler;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import java.net.ConnectException;
 import java.net.UnknownHostException;
+import java.util.stream.Collectors;
 import org.apache.ignite.cli.core.exception.ExceptionHandler;
 import org.apache.ignite.cli.core.exception.ExceptionWriter;
 import org.apache.ignite.cli.core.exception.IgniteCliApiException;
+import org.apache.ignite.cli.core.style.component.ErrorUiComponent;
+import org.apache.ignite.cli.core.style.component.ErrorUiComponent.ErrorComponentBuilder;
+import org.apache.ignite.cli.core.style.element.UiElements;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.rest.client.invoker.ApiException;
+import org.apache.ignite.rest.client.model.Problem;
+import org.jetbrains.annotations.NotNull;
 
 /**
  * Exception handler for {@link IgniteCliApiException}.
@@ -32,33 +40,62 @@ import org.apache.ignite.rest.client.invoker.ApiException;
 public class IgniteCliApiExceptionHandler implements ExceptionHandler<IgniteCliApiException> {
     private static final IgniteLogger LOG = Loggers.forClass(IgniteCliApiExceptionHandler.class);
 
+    private static final ObjectMapper objectMapper = new ObjectMapper();
+
     @Override
     public int handle(ExceptionWriter err, IgniteCliApiException e) {
-        String message;
+        ErrorUiComponent.ErrorComponentBuilder errorComponentBuilder = ErrorUiComponent.builder();
 
         if (e.getCause() instanceof ApiException) {
             ApiException cause = (ApiException) e.getCause();
             Throwable apiCause = cause.getCause();
             if (apiCause instanceof UnknownHostException) {
-                message = "Could not determine IP address when connecting to URL [url=" + e.getUrl() + ']';
+                errorComponentBuilder
+                        .header("Unknown host: %s", UiElements.url(e.getUrl()));
             } else if (apiCause instanceof ConnectException) {
-                message = "Could not connect to URL [url=" + e.getUrl() + ']';
+                errorComponentBuilder
+                        .header("Node unavailable")
+                        .details("Could not connect to node with URL %s", UiElements.url(e.getUrl()));
             } else if (apiCause != null) {
-                message = apiCause.getMessage();
+                errorComponentBuilder.header(apiCause.getMessage());
             } else {
-                message = "An error occurred [errorCode=" + cause.getCode() + ", response=" + cause.getResponseBody() + ']';
+                tryToExtractProblem(errorComponentBuilder, cause);
             }
         } else {
-            message = e.getCause() != e ? e.getCause().getMessage() : e.getMessage();
+            errorComponentBuilder.header(e.getCause() != e ? e.getCause().getMessage() : e.getMessage());
         }
 
-        LOG.error(message, e);
+        ErrorUiComponent errorComponent = errorComponentBuilder.build();
+
+        LOG.error(errorComponent.header(), e);
 
-        err.write(message);
+        err.write(errorComponent.render());
 
         return 1;
     }
 
+    private static void tryToExtractProblem(ErrorComponentBuilder errorComponentBuilder, ApiException cause) {
+        try {
+            Problem problem = objectMapper.readValue(cause.getResponseBody(), Problem.class);
+            if (!problem.getInvalidParams().isEmpty()) {
+                errorComponentBuilder.details(extractInvalidParams(problem));
+            }
+            errorComponentBuilder
+                    .header(problem.getDetail())
+                    .errorCode(problem.getCode())
+                    .traceId(problem.getTraceId());
+        } catch (JsonProcessingException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @NotNull
+    private static String extractInvalidParams(Problem problem) {
+        return problem.getInvalidParams().stream()
+                .map(invalidParam -> "" + invalidParam.getName() + ": " + invalidParam.getReason())
+                .collect(Collectors.joining(System.lineSeparator()));
+    }
+
     @Override
     public Class<IgniteCliApiException> applicableException() {
         return IgniteCliApiException.class;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/IgniteCliExceptionHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/IgniteCliExceptionHandler.java
index 0d98620803..4e539209d3 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/IgniteCliExceptionHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/IgniteCliExceptionHandler.java
@@ -19,6 +19,7 @@ package org.apache.ignite.cli.core.exception.handler;
 
 import org.apache.ignite.cli.core.exception.ExceptionHandler;
 import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.cli.core.style.component.ErrorUiComponent;
 import org.apache.ignite.cli.deprecated.IgniteCliException;
 
 /**
@@ -27,7 +28,10 @@ import org.apache.ignite.cli.deprecated.IgniteCliException;
 public class IgniteCliExceptionHandler implements ExceptionHandler<IgniteCliException> {
     @Override
     public int handle(ExceptionWriter err, IgniteCliException e) {
-        err.write(e.getMessage());
+        err.write(
+                ErrorUiComponent.fromHeader(e.getMessage()).render()
+        );
+
         return 1;
     }
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/PicocliExecutionExceptionHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/PicocliExecutionExceptionHandler.java
index 9c96a8b0d6..120d192a33 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/PicocliExecutionExceptionHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/PicocliExecutionExceptionHandler.java
@@ -30,6 +30,6 @@ public class PicocliExecutionExceptionHandler implements IExecutionExceptionHand
 
     @Override
     public int handleExecutionException(Exception ex, CommandLine commandLine, ParseResult parseResult) {
-        return exceptionHandlers.handleException(System.err::println, ex);
+        return exceptionHandlers.handleException(commandLine.getErr()::println, ex);
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ProfileNotFoundExceptionHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ProfileNotFoundExceptionHandler.java
index a410dd0bc4..dc2cf7a09d 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ProfileNotFoundExceptionHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ProfileNotFoundExceptionHandler.java
@@ -20,6 +20,7 @@ package org.apache.ignite.cli.core.exception.handler;
 import org.apache.ignite.cli.config.ProfileNotFoundException;
 import org.apache.ignite.cli.core.exception.ExceptionHandler;
 import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.cli.core.style.component.ErrorUiComponent;
 
 /**
  * Handler for {@link ProfileNotFoundException}.
@@ -27,7 +28,10 @@ import org.apache.ignite.cli.core.exception.ExceptionWriter;
 public class ProfileNotFoundExceptionHandler implements ExceptionHandler<ProfileNotFoundException> {
     @Override
     public int handle(ExceptionWriter err, ProfileNotFoundException e) {
-        err.write(e.getMessage());
+        err.write(
+                ErrorUiComponent.fromHeader(e.getMessage()).render()
+        );
+
         return 1;
     }
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/SectionAlreadyExistsExceptionHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/SectionAlreadyExistsExceptionHandler.java
index aa868ca8a1..e3af26c13b 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/SectionAlreadyExistsExceptionHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/SectionAlreadyExistsExceptionHandler.java
@@ -20,6 +20,7 @@ package org.apache.ignite.cli.core.exception.handler;
 import org.apache.ignite.cli.config.ini.SectionAlreadyExistsException;
 import org.apache.ignite.cli.core.exception.ExceptionHandler;
 import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.cli.core.style.component.ErrorUiComponent;
 
 /**
  * Handler for {@link SectionAlreadyExistsException}.
@@ -27,7 +28,10 @@ import org.apache.ignite.cli.core.exception.ExceptionWriter;
 public class SectionAlreadyExistsExceptionHandler implements ExceptionHandler<SectionAlreadyExistsException> {
     @Override
     public int handle(ExceptionWriter err, SectionAlreadyExistsException e) {
-        err.write(e.getMessage());
+        err.write(
+                ErrorUiComponent.fromHeader(e.getMessage()).render()
+        );
+
         return 1;
     }
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ShowConfigExceptionHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ShowConfigExceptionHandler.java
new file mode 100644
index 0000000000..ec7528067e
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/ShowConfigExceptionHandler.java
@@ -0,0 +1,49 @@
+/*
+ * 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.cli.core.exception.handler;
+
+import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.cli.core.exception.IgniteCliApiException;
+import org.apache.ignite.cli.core.style.component.ErrorUiComponent;
+import org.apache.ignite.cli.core.style.element.UiElements;
+import org.apache.ignite.rest.client.invoker.ApiException;
+
+/**
+ * This exception handler is used only for `cluster config show` command and handles a tricky case with the 500 error on not initialized
+ * cluster.
+ */
+public class ShowConfigExceptionHandler extends IgniteCliApiExceptionHandler {
+    @Override
+    public int handle(ExceptionWriter err, IgniteCliApiException e) {
+        if (e.getCause() instanceof ApiException) {
+            ApiException apiException = (ApiException) e.getCause();
+            if (apiException.getCode() == 500) { //TODO: https://issues.apache.org/jira/browse/IGNITE-17510
+                err.write(
+                        ErrorUiComponent.builder()
+                                .header("Cannot show cluster config")
+                                .details("Probably, you have not initialized the cluster, try to run %s command",
+                                        UiElements.command("cluster init"))
+                                .build()
+                                .render()
+                );
+                return 1;
+            }
+        }
+        return super.handle(err, e);
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/SqlExceptionHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/SqlExceptionHandler.java
index 813366a307..d3d62e87b7 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/SqlExceptionHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/SqlExceptionHandler.java
@@ -20,6 +20,7 @@ package org.apache.ignite.cli.core.exception.handler;
 import java.sql.SQLException;
 import org.apache.ignite.cli.core.exception.ExceptionHandler;
 import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.cli.core.style.component.ErrorUiComponent;
 import org.apache.ignite.internal.jdbc.proto.SqlStateCode;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
@@ -30,35 +31,39 @@ import org.apache.ignite.internal.logger.Loggers;
 public class SqlExceptionHandler implements ExceptionHandler<SQLException> {
     private static final IgniteLogger LOG = Loggers.forClass(SqlExceptionHandler.class);
 
-    public static final String PARSING_ERROR_MESSAGE = "SQL query parsing error: %s";
+    public static final String PARSING_ERROR_MESSAGE = "SQL query parsing error";
 
-    public static final String INVALID_PARAMETER_MESSAGE = "Invalid parameter value.";
+    public static final String INVALID_PARAMETER_MESSAGE = "Invalid parameter value";
 
-    public static final String CLIENT_CONNECTION_FAILED_MESSAGE = "Connection failed.";
+    public static final String CLIENT_CONNECTION_FAILED_MESSAGE = "Connection failed";
 
-    public static final String CONNECTION_BROKE_MESSAGE = "Connection error.";
+    public static final String CONNECTION_BROKE_MESSAGE = "Connection error";
 
     @Override
     public int handle(ExceptionWriter err, SQLException e) {
+        var errorComponentBuilder = ErrorUiComponent.builder();
+
         switch (e.getSQLState()) {
             case SqlStateCode.CONNECTION_FAILURE:
             case SqlStateCode.CONNECTION_CLOSED:
             case SqlStateCode.CONNECTION_REJECTED:
-                err.write(CONNECTION_BROKE_MESSAGE);
+                errorComponentBuilder.header(CONNECTION_BROKE_MESSAGE);
                 break;
             case SqlStateCode.PARSING_EXCEPTION:
-                err.write(String.format(PARSING_ERROR_MESSAGE, e.getMessage()));
+                errorComponentBuilder.header(PARSING_ERROR_MESSAGE).details(e.getMessage());
                 break;
             case SqlStateCode.INVALID_PARAMETER_VALUE:
-                err.write(INVALID_PARAMETER_MESSAGE);
+                errorComponentBuilder.header(INVALID_PARAMETER_MESSAGE);
                 break;
             case SqlStateCode.CLIENT_CONNECTION_FAILED:
-                err.write(CLIENT_CONNECTION_FAILED_MESSAGE);
+                errorComponentBuilder.header(CLIENT_CONNECTION_FAILED_MESSAGE);
                 break;
             default:
                 LOG.error("Unrecognized error", e);
-                err.write("Unrecognized error while process SQL query.");
+                errorComponentBuilder.header("Unrecognized error while process SQL query");
         }
+
+        err.write(errorComponentBuilder.build().render());
         return 1;
     }
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/TimeoutExceptionHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/TimeoutExceptionHandler.java
index 70c81ec800..9b67cc8fa0 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/TimeoutExceptionHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/TimeoutExceptionHandler.java
@@ -20,6 +20,7 @@ package org.apache.ignite.cli.core.exception.handler;
 import java.util.concurrent.TimeoutException;
 import org.apache.ignite.cli.core.exception.ExceptionHandler;
 import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.cli.core.style.component.ErrorUiComponent;
 import org.apache.ignite.internal.logger.IgniteLogger;
 import org.apache.ignite.internal.logger.Loggers;
 
@@ -32,7 +33,14 @@ public class TimeoutExceptionHandler implements ExceptionHandler<TimeoutExceptio
     @Override
     public int handle(ExceptionWriter err, TimeoutException e) {
         LOG.error("Timeout exception", e);
-        err.write("Command failed with timeout.");
+        err.write(
+                ErrorUiComponent.builder()
+                        .header("The command is running for too long")
+                        .details(e.getMessage())
+                        .build()
+                        .render()
+        );
+
         return 1;
     }
 
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/UnknownCommandExceptionHandler.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/UnknownCommandExceptionHandler.java
index 5750ecdcdd..728bf4e732 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/UnknownCommandExceptionHandler.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/UnknownCommandExceptionHandler.java
@@ -19,6 +19,7 @@ package org.apache.ignite.cli.core.exception.handler;
 
 import org.apache.ignite.cli.core.exception.ExceptionHandler;
 import org.apache.ignite.cli.core.exception.ExceptionWriter;
+import org.apache.ignite.cli.core.style.component.ErrorUiComponent;
 import org.jline.console.impl.SystemRegistryImpl.UnknownCommandException;
 
 /**
@@ -29,7 +30,9 @@ public class UnknownCommandExceptionHandler implements ExceptionHandler<UnknownC
 
     @Override
     public int handle(ExceptionWriter err, UnknownCommandException e) {
-        err.write(e.getMessage());
+        err.write(
+                ErrorUiComponent.fromHeader(e.getMessage()).render()
+        );
         // This exception is only thrown in the REPL mode so the return value is irrelevant, but we use 2 to keep it consistent
         return 2;
     }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/flow/builder/FlowBuilderImpl.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/flow/builder/FlowBuilderImpl.java
index 2de366e741..ffd8559f42 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/core/flow/builder/FlowBuilderImpl.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/flow/builder/FlowBuilderImpl.java
@@ -21,7 +21,6 @@ import java.io.PrintWriter;
 import java.util.List;
 import java.util.function.Function;
 import java.util.function.Predicate;
-import org.apache.ignite.cli.commands.decorators.DefaultDecoratorRegistry;
 import org.apache.ignite.cli.core.decorator.DecoratorRegistry;
 import org.apache.ignite.cli.core.exception.ExceptionHandler;
 import org.apache.ignite.cli.core.exception.ExceptionHandlers;
@@ -33,6 +32,7 @@ import org.apache.ignite.cli.core.flow.Flowable;
 import org.apache.ignite.cli.core.flow.question.QuestionAnswer;
 import org.apache.ignite.cli.core.flow.question.QuestionAskerFactory;
 import org.apache.ignite.cli.core.repl.context.CommandLineContextProvider;
+import org.apache.ignite.cli.decorators.DefaultDecoratorRegistry;
 
 /**
  * Implementation of {@link FlowBuilder}.
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/flow/builder/Flows.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/flow/builder/Flows.java
index af62836012..9070efd5af 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/core/flow/builder/Flows.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/flow/builder/Flows.java
@@ -30,14 +30,13 @@ import org.apache.ignite.cli.core.flow.question.AcceptedQuestionAnswer;
 import org.apache.ignite.cli.core.flow.question.InterruptQuestionAnswer;
 import org.apache.ignite.cli.core.flow.question.QuestionAnswer;
 import org.apache.ignite.cli.core.flow.question.QuestionAskerFactory;
+import org.apache.ignite.cli.core.style.component.QuestionUiComponent;
 
 /**
  * Helper class for operating and creating {@link Flow} and {@link FlowBuilder}.
  */
 public final class Flows {
-
     private Flows() {
-
     }
 
     /**
@@ -140,4 +139,18 @@ public final class Flows {
                 )
                 .then(Flows.mono(unused -> onAccept.get()));
     }
+
+    /**
+     * Create new {@link FlowBuilder} which starts from yes/no question and pass the result of the @{code onAccept}
+     * call on positive answer or interrupts the flow on negative answer.
+     *
+     * @param question question UI component.
+     * @param onAccept callback to call on positive answer
+     * @param <I> input type.
+     * @param <O> output type.
+     * @return new {@link FlowBuilder}.
+     */
+    public static <I, O> FlowBuilder<I, O> acceptQuestion(QuestionUiComponent question, Supplier<O> onAccept) {
+        return acceptQuestion(question.render(), onAccept);
+    }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/style/AnsiStringSupport.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/AnsiStringSupport.java
index 0d3ce6de44..e9e20a6e62 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/core/style/AnsiStringSupport.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/AnsiStringSupport.java
@@ -33,26 +33,63 @@ public final class AnsiStringSupport {
         return new Fg(color);
     }
 
+    /**
+     * Can mark the string as a ANSI string.
+     */
+    public interface Marker {
+        String mark(String content);
+    }
+
     /**
      * Dsl for ansi fg: takes color and marks string like: mytext -> @|fg(10) mytext|@ where 10 is the ansi color.
      */
-    public static class Fg {
+    public static class Fg implements Marker {
         private final Color color;
+        private Style style;
 
         private Fg(Color color) {
             this.color = color;
         }
 
+        public Fg with(Style style) {
+            this.style = style;
+            return this;
+        }
+
+        /**
+         * Marks given text with the configured before style.
+         */
         public String mark(String textToMark) {
+            if (style == Style.BOLD) {
+                return String.format("@|fg(%d),bold %s|@", color.code, textToMark);
+            }
             return String.format("@|fg(%d) %s|@", color.code, textToMark);
         }
     }
 
+    /**
+     * Represents the text style.
+     */
+    public enum Style implements Marker {
+        BOLD("bold"),
+        UNDERLINE("underline");
+
+        private final String value;
+
+        Style(String value) {
+            this.value = value;
+        }
+
+        public String mark(String textToMark) {
+            return String.format("@|%s %s|@", value, textToMark);
+        }
+    }
+
     /**
      * Represents ansi colors that are used in CLI.
      */
     public enum Color {
-        RED(1), GREEN(2), YELLOW(3);
+        RED(1), GREEN(2), YELLOW(3), GRAY(246);
 
         Color(int code) {
             this.code = code;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecorator.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/component/CommonMessages.java
similarity index 50%
copy from modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecorator.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/style/component/CommonMessages.java
index c2f1518b54..7bbf3f3518 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecorator.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/component/CommonMessages.java
@@ -15,21 +15,23 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.commands.decorators;
+package org.apache.ignite.cli.core.style.component;
 
-import org.apache.ignite.cli.core.decorator.Decorator;
-import org.apache.ignite.cli.core.decorator.TerminalOutput;
+import org.apache.ignite.cli.core.style.element.UiElements;
 
 /**
- * Default decorator that calls toString method.
- *
- * @param <I> Input type.
+ * Common UI messages.
  */
-public class DefaultDecorator<I> implements Decorator<I, TerminalOutput> {
+public class CommonMessages {
+    public static MessageUiComponent CONNECT_OR_USE_CLUSTER_URL_MESSAGE = MessageUiComponent.builder()
+            .message("You are not connected to node")
+            .hint("Run %s command or use %s option",
+                    UiElements.command("connect"), UiElements.option("--cluster-url"))
+            .build();
 
-    /** {@inheritDoc} */
-    @Override
-    public TerminalOutput decorate(I data) {
-        return data::toString;
-    }
+    public static MessageUiComponent CONNECT_OR_USE_NODE_URL_MESSAGE = MessageUiComponent.builder()
+            .message("You are not connected to node")
+            .hint("Run %s command or use %s  option",
+                    UiElements.command("connect"), UiElements.option("--node-url"))
+            .build();
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/style/component/ErrorUiComponent.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/component/ErrorUiComponent.java
new file mode 100644
index 0000000000..96d4267429
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/component/ErrorUiComponent.java
@@ -0,0 +1,132 @@
+/*
+ * 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.cli.core.style.component;
+
+import static org.apache.ignite.cli.core.style.AnsiStringSupport.ansi;
+import static org.apache.ignite.cli.core.style.AnsiStringSupport.fg;
+
+import java.util.UUID;
+import org.apache.ignite.cli.core.style.AnsiStringSupport.Color;
+import org.apache.ignite.cli.core.style.AnsiStringSupport.Style;
+import org.apache.ignite.cli.core.style.element.UiElement;
+import org.apache.ignite.cli.core.style.element.UiString;
+
+/**
+ * UI component that represent any error message.
+ */
+public class ErrorUiComponent implements UiComponent {
+    private final String header;
+
+    private final UiElement[] headerUiElements;
+
+    private final String details;
+
+    private final UiElement[] detailsUiElements;
+
+    private final UUID traceId;
+
+    private final String errorCode;
+
+    private ErrorUiComponent(
+            String header, UiElement[] headerUiElements,
+            String details, UiElement[] detailsUiElements,
+            UUID traceId,
+            String errorCode) {
+        this.header = header;
+        this.headerUiElements = headerUiElements;
+        this.details = details;
+        this.detailsUiElements = detailsUiElements;
+        this.traceId = traceId;
+        this.errorCode = errorCode;
+    }
+
+    /** Creates ErrorComponent from given header. */
+    public static ErrorUiComponent fromHeader(String header) {
+        return builder().header(header).build();
+    }
+
+    /** Builder. */
+    public static ErrorComponentBuilder builder() {
+        return new ErrorComponentBuilder();
+    }
+
+    public String header() {
+        return header;
+    }
+
+    public String details() {
+        return details;
+    }
+
+    @Override
+    public String render() {
+        return ansi(
+                (errorCode == null ? "" : fg(Color.GRAY).mark(errorCode))
+                        + traceDetails()
+                        + fg(Color.RED).with(Style.BOLD).mark(ansi(UiString.format(header, headerUiElements)))
+                        + (details == null ? "" : System.lineSeparator() + UiString.format(details, detailsUiElements))
+        );
+    }
+
+    private String traceDetails() {
+        return traceId == null ? "" : fg(Color.GRAY).mark(" Trace ID: " + traceId + System.lineSeparator());
+    }
+
+    /** Builder. */
+    public static class ErrorComponentBuilder {
+        private String header;
+
+        private UiElement[] headerUiElements;
+
+        private String details;
+
+        private UiElement[] detailsUiElement;
+
+        private UUID traceId;
+
+        private String errorCode;
+
+        /** Sets header. */
+        public ErrorComponentBuilder header(String header, UiElement... uiElements) {
+            this.header = header;
+            this.headerUiElements = uiElements;
+            return this;
+        }
+
+        /** Sets details. */
+        public ErrorComponentBuilder details(String details, UiElement... uiElements) {
+            this.details = details;
+            this.detailsUiElement = uiElements;
+            return this;
+        }
+
+        public ErrorComponentBuilder traceId(UUID traceId) {
+            this.traceId = traceId;
+            return this;
+        }
+
+        public ErrorComponentBuilder errorCode(String errorCode) {
+            this.errorCode = errorCode;
+            return this;
+        }
+
+        public ErrorUiComponent build() {
+            return new ErrorUiComponent(header, headerUiElements, details, detailsUiElement, traceId, errorCode);
+        }
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/style/component/MessageUiComponent.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/component/MessageUiComponent.java
new file mode 100644
index 0000000000..184d013391
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/component/MessageUiComponent.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.core.style.component;
+
+import static org.apache.ignite.cli.core.style.AnsiStringSupport.ansi;
+
+import org.apache.ignite.cli.core.style.element.UiElement;
+import org.apache.ignite.cli.core.style.element.UiString;
+
+/**
+ * UI component that represents a message.
+ */
+public class MessageUiComponent implements UiComponent {
+    private final String message;
+
+    private final UiElement[] messageUiElements;
+
+    private final String hint;
+
+    private final UiElement[] hintUiElements;
+
+    private MessageUiComponent(
+            String message,
+            UiElement[] messageUiElements,
+            String hint,
+            UiElement[] hintUiElements) {
+        this.message = message;
+        this.messageUiElements = messageUiElements;
+        this.hint = hint;
+        this.hintUiElements = hintUiElements;
+    }
+
+    @Override
+    public String render() {
+        String messageString = UiString.format(message, messageUiElements);
+        String hintString = UiString.format(hint, hintUiElements);
+        return ansi(
+                messageString
+                        + (hintString == null ? "" : System.lineSeparator() + hintString)
+        );
+    }
+
+    public static MessageUiComponent fromMessage(String message, UiElement... messageUiElements) {
+        return builder().message(message, messageUiElements).build();
+    }
+
+    /** Builder. */
+    public static MessageComponentBuilder builder() {
+        return new MessageComponentBuilder();
+    }
+
+    /** Builder. */
+    public static class MessageComponentBuilder {
+        private String message;
+
+        private String hint;
+
+        private UiElement[] messageUiElements;
+
+        private UiElement[] hintUiElements;
+
+        /** Sets message. */
+        public MessageComponentBuilder message(String message, UiElement... uiElements) {
+            this.message = message;
+            this.messageUiElements = uiElements;
+            return this;
+        }
+
+        /** Sets hint. */
+        public MessageComponentBuilder hint(String hint, UiElement... uiElements) {
+            this.hint = hint;
+            this.hintUiElements = uiElements;
+            return this;
+        }
+
+        public MessageUiComponent build() {
+            return new MessageUiComponent(message, messageUiElements, hint, hintUiElements);
+        }
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/style/component/QuestionUiComponent.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/component/QuestionUiComponent.java
new file mode 100644
index 0000000000..7d9a63fa9e
--- /dev/null
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/component/QuestionUiComponent.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.core.style.component;
+
+import static org.apache.ignite.cli.core.style.AnsiStringSupport.ansi;
+
+import org.apache.ignite.cli.core.style.element.UiElement;
+import org.apache.ignite.cli.core.style.element.UiString;
+
+/**
+ * UI component that represents a question.
+ */
+public class QuestionUiComponent implements UiComponent {
+    private final String question;
+
+    private final UiElement[] questionUiElements;
+
+    private QuestionUiComponent(
+            String question,
+            UiElement[] questionUiElements) {
+        this.question = question;
+        this.questionUiElements = questionUiElements;
+    }
+
+    @Override
+    public String render() {
+        return ansi(UiString.format(question, questionUiElements));
+    }
+
+    public static QuestionUiComponent fromQuestion(String question, UiElement... questionUiElements) {
+        return builder().question(question, questionUiElements).build();
+    }
+
+    /** Builder. */
+    public static MessageComponentBuilder builder() {
+        return new MessageComponentBuilder();
+    }
+
+    /** Builder. */
+    public static class MessageComponentBuilder {
+        private String question;
+
+        private UiElement[] questionUiElements;
+
+        /** Sets question. */
+        public MessageComponentBuilder question(String question, UiElement... uiElements) {
+            this.question = question;
+            this.questionUiElements = uiElements;
+            return this;
+        }
+
+        public QuestionUiComponent build() {
+            return new QuestionUiComponent(question, questionUiElements);
+        }
+    }
+}
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/SectionAlreadyExistsException.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/component/UiComponent.java
similarity index 73%
copy from modules/cli/src/main/java/org/apache/ignite/cli/config/ini/SectionAlreadyExistsException.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/style/component/UiComponent.java
index 72b69ffe7e..b63f8fa083 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/SectionAlreadyExistsException.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/component/UiComponent.java
@@ -15,13 +15,11 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.config.ini;
+package org.apache.ignite.cli.core.style.component;
 
 /**
- * Exception when already created INI section trying to create.
+ * UI component that can render to ANSI String.
  */
-public class SectionAlreadyExistsException extends RuntimeException {
-    public SectionAlreadyExistsException(String name) {
-        super("Section " + name + " already exists.");
-    }
+public interface UiComponent {
+    String render();
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecorator.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/element/MarkedUiElement.java
similarity index 59%
copy from modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecorator.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/style/element/MarkedUiElement.java
index c2f1518b54..56522c1f3b 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecorator.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/element/MarkedUiElement.java
@@ -15,21 +15,30 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.commands.decorators;
+package org.apache.ignite.cli.core.style.element;
 
-import org.apache.ignite.cli.core.decorator.Decorator;
-import org.apache.ignite.cli.core.decorator.TerminalOutput;
+import org.apache.ignite.cli.core.style.AnsiStringSupport.Marker;
 
 /**
- * Default decorator that calls toString method.
- *
- * @param <I> Input type.
+ * IU element that is marked with provided ANSI marker.
  */
-public class DefaultDecorator<I> implements Decorator<I, TerminalOutput> {
+public class MarkedUiElement implements UiElement {
+    private final String content;
+
+    private final Marker marker;
+
+    MarkedUiElement(String content, Marker marker) {
+        this.content = content;
+        this.marker = marker;
+    }
+
+    @Override
+    public String represent() {
+        return marker.mark(content);
+    }
 
-    /** {@inheritDoc} */
     @Override
-    public TerminalOutput decorate(I data) {
-        return data::toString;
+    public String toString() {
+        return represent();
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/SectionAlreadyExistsException.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/element/UiElement.java
similarity index 73%
copy from modules/cli/src/main/java/org/apache/ignite/cli/config/ini/SectionAlreadyExistsException.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/style/element/UiElement.java
index 72b69ffe7e..a808770e69 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/SectionAlreadyExistsException.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/element/UiElement.java
@@ -15,13 +15,12 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.config.ini;
+package org.apache.ignite.cli.core.style.element;
 
 /**
- * Exception when already created INI section trying to create.
+ * Can represent UI Element as an ANSI string.
  */
-public class SectionAlreadyExistsException extends RuntimeException {
-    public SectionAlreadyExistsException(String name) {
-        super("Section " + name + " already exists.");
-    }
+public interface UiElement {
+    /** Represents the UI Element as ansi String. For example '@|fg(1) text |@'. */
+    String represent();
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/NodeStatusDecorator.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/element/UiElements.java
similarity index 54%
copy from modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/NodeStatusDecorator.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/style/element/UiElements.java
index 2cb9c08cfb..224130bb7a 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/NodeStatusDecorator.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/element/UiElements.java
@@ -15,30 +15,34 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.commands.decorators;
+package org.apache.ignite.cli.core.style.element;
 
-import static org.apache.ignite.cli.core.style.AnsiStringSupport.ansi;
 import static org.apache.ignite.cli.core.style.AnsiStringSupport.fg;
 
-import org.apache.ignite.cli.call.node.status.NodeStatus;
-import org.apache.ignite.cli.call.node.status.State;
-import org.apache.ignite.cli.core.decorator.Decorator;
-import org.apache.ignite.cli.core.decorator.TerminalOutput;
 import org.apache.ignite.cli.core.style.AnsiStringSupport.Color;
+import org.apache.ignite.cli.core.style.AnsiStringSupport.Style;
 
 /**
- * Decorator for {@link NodeStatus}.
+ * Defines all UI Elements that are used in the CLI.
  */
-public class NodeStatusDecorator implements Decorator<NodeStatus, TerminalOutput> {
+public class UiElements {
+    public static UiElement url(String content) {
+        return new MarkedUiElement(content, Style.UNDERLINE);
+    }
+
+    public static UiElement command(String content) {
+        return new MarkedUiElement(content, Style.BOLD);
+    }
 
-    @Override
-    public TerminalOutput decorate(NodeStatus data) {
-        Color c = data.state().equals(State.STARTED) ? Color.GREEN : Color.YELLOW;
+    public static UiElement option(String content) {
+        return new MarkedUiElement(content, fg(Color.YELLOW));
+    }
+
+    public static UiElement done() {
+        return new MarkedUiElement("Done", fg(Color.GREEN).with(Style.BOLD));
+    }
 
-        return () -> ansi(
-                "[name: %s, state: %s]",
-                data.name(),
-                fg(c).mark(data.state().name().toLowerCase())
-        );
+    public static UiElement yesNo() {
+        return new MarkedUiElement("[Y/n]", fg(Color.GRAY));
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecorator.java b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/element/UiString.java
similarity index 61%
copy from modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecorator.java
copy to modules/cli/src/main/java/org/apache/ignite/cli/core/style/element/UiString.java
index c2f1518b54..ee92072444 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecorator.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/core/style/element/UiString.java
@@ -15,21 +15,22 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.commands.decorators;
+package org.apache.ignite.cli.core.style.element;
 
-import org.apache.ignite.cli.core.decorator.Decorator;
-import org.apache.ignite.cli.core.decorator.TerminalOutput;
+import java.util.Arrays;
 
 /**
- * Default decorator that calls toString method.
- *
- * @param <I> Input type.
+ * Can format the string with UI Elements.
  */
-public class DefaultDecorator<I> implements Decorator<I, TerminalOutput> {
+public class UiString {
+    /** Accepts the String template with UI Elements. */
+    public static String format(String template, UiElement... elements) {
+        if (elements == null || elements.length == 0) {
+            return template;
+        }
+
+        Object[] elementsAsObjects = Arrays.stream(elements).map(Object.class::cast).toArray();
 
-    /** {@inheritDoc} */
-    @Override
-    public TerminalOutput decorate(I data) {
-        return data::toString;
+        return String.format(template, elementsAsObjects);
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/ClusterStatusDecorator.java b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/ClusterStatusDecorator.java
similarity index 97%
rename from modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/ClusterStatusDecorator.java
rename to modules/cli/src/main/java/org/apache/ignite/cli/decorators/ClusterStatusDecorator.java
index 01371aaa9e..2935b7d058 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/ClusterStatusDecorator.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/ClusterStatusDecorator.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.commands.decorators;
+package org.apache.ignite.cli.decorators;
 
 import static org.apache.ignite.cli.core.style.AnsiStringSupport.ansi;
 import static org.apache.ignite.cli.core.style.AnsiStringSupport.fg;
@@ -29,7 +29,6 @@ import org.apache.ignite.cli.core.style.AnsiStringSupport.Color;
  * Decorator for {@link ClusterStatus}.
  */
 public class ClusterStatusDecorator implements Decorator<ClusterStatus, TerminalOutput> {
-
     @Override
     public TerminalOutput decorate(ClusterStatus data) {
         return data.isInitialized()
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecorator.java b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/DefaultDecorator.java
similarity index 95%
rename from modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecorator.java
rename to modules/cli/src/main/java/org/apache/ignite/cli/decorators/DefaultDecorator.java
index c2f1518b54..811db85898 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecorator.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/DefaultDecorator.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.commands.decorators;
+package org.apache.ignite.cli.decorators;
 
 import org.apache.ignite.cli.core.decorator.Decorator;
 import org.apache.ignite.cli.core.decorator.TerminalOutput;
@@ -26,7 +26,6 @@ import org.apache.ignite.cli.core.decorator.TerminalOutput;
  * @param <I> Input type.
  */
 public class DefaultDecorator<I> implements Decorator<I, TerminalOutput> {
-
     /** {@inheritDoc} */
     @Override
     public TerminalOutput decorate(I data) {
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecoratorRegistry.java b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/DefaultDecoratorRegistry.java
similarity index 97%
rename from modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecoratorRegistry.java
rename to modules/cli/src/main/java/org/apache/ignite/cli/decorators/DefaultDecoratorRegistry.java
index 26b95d839a..20f483219a 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/DefaultDecoratorRegistry.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/DefaultDecoratorRegistry.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.commands.decorators;
+package org.apache.ignite.cli.decorators;
 
 import org.apache.ignite.cli.call.cluster.status.ClusterStatus;
 import org.apache.ignite.cli.call.configuration.JsonString;
@@ -29,7 +29,6 @@ import org.apache.ignite.cli.sql.table.Table;
  * Default set of {@link org.apache.ignite.cli.core.decorator.Decorator}.
  */
 public class DefaultDecoratorRegistry extends DecoratorRegistry {
-
     /**
      * Constructor.
      */
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/JsonDecorator.java b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/JsonDecorator.java
similarity index 97%
rename from modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/JsonDecorator.java
rename to modules/cli/src/main/java/org/apache/ignite/cli/decorators/JsonDecorator.java
index da54585629..8c83da7030 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/JsonDecorator.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/JsonDecorator.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.commands.decorators;
+package org.apache.ignite.cli.decorators;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
@@ -28,7 +28,6 @@ import org.apache.ignite.cli.core.decorator.TerminalOutput;
  * Pretty json decorator.
  */
 public class JsonDecorator implements Decorator<JsonString, TerminalOutput> {
-
     /** {@inheritDoc} */
     @Override
     public TerminalOutput decorate(JsonString json) {
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/NodeStatusDecorator.java b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/NodeStatusDecorator.java
similarity index 97%
rename from modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/NodeStatusDecorator.java
rename to modules/cli/src/main/java/org/apache/ignite/cli/decorators/NodeStatusDecorator.java
index 2cb9c08cfb..de2d73b899 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/NodeStatusDecorator.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/NodeStatusDecorator.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.commands.decorators;
+package org.apache.ignite.cli.decorators;
 
 import static org.apache.ignite.cli.core.style.AnsiStringSupport.ansi;
 import static org.apache.ignite.cli.core.style.AnsiStringSupport.fg;
@@ -30,7 +30,6 @@ import org.apache.ignite.cli.core.style.AnsiStringSupport.Color;
  * Decorator for {@link NodeStatus}.
  */
 public class NodeStatusDecorator implements Decorator<NodeStatus, TerminalOutput> {
-
     @Override
     public TerminalOutput decorate(NodeStatus data) {
         Color c = data.state().equals(State.STARTED) ? Color.GREEN : Color.YELLOW;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/ProfileDecorator.java b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/ProfileDecorator.java
similarity index 96%
rename from modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/ProfileDecorator.java
rename to modules/cli/src/main/java/org/apache/ignite/cli/decorators/ProfileDecorator.java
index c9d4271c93..468b8eac2f 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/ProfileDecorator.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/ProfileDecorator.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.commands.decorators;
+package org.apache.ignite.cli.decorators;
 
 import java.util.stream.Collectors;
 import org.apache.ignite.cli.config.Profile;
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/SqlQueryResultDecorator.java b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/SqlQueryResultDecorator.java
similarity index 96%
rename from modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/SqlQueryResultDecorator.java
rename to modules/cli/src/main/java/org/apache/ignite/cli/decorators/SqlQueryResultDecorator.java
index 619fcffbc0..5bf72ca27f 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/SqlQueryResultDecorator.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/SqlQueryResultDecorator.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.commands.decorators;
+package org.apache.ignite.cli.decorators;
 
 import org.apache.ignite.cli.core.decorator.Decorator;
 import org.apache.ignite.cli.core.decorator.TerminalOutput;
@@ -25,7 +25,6 @@ import org.apache.ignite.cli.sql.SqlQueryResult;
  * Composite decorator for {@link SqlQueryResult}.
  */
 public class SqlQueryResultDecorator implements Decorator<SqlQueryResult, TerminalOutput> {
-
     private final TableDecorator tableDecorator = new TableDecorator();
 
     private final DefaultDecorator<String> messageDecorator = new DefaultDecorator<>();
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/TableDecorator.java b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/TableDecorator.java
similarity index 96%
rename from modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/TableDecorator.java
rename to modules/cli/src/main/java/org/apache/ignite/cli/decorators/TableDecorator.java
index d1b8b72d73..c6ce5806e6 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/TableDecorator.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/TableDecorator.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.commands.decorators;
+package org.apache.ignite.cli.decorators;
 
 import com.jakewharton.fliptables.FlipTableConverters;
 import org.apache.ignite.cli.core.decorator.Decorator;
@@ -26,7 +26,6 @@ import org.apache.ignite.cli.sql.table.Table;
  * Implementation of {@link Decorator} for {@link Table}.
  */
 public class TableDecorator implements Decorator<Table, TerminalOutput> {
-
     /**
      * Transform {@link Table} to {@link TerminalOutput}.
      *
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/TopologyDecorator.java b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/TopologyDecorator.java
similarity index 97%
rename from modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/TopologyDecorator.java
rename to modules/cli/src/main/java/org/apache/ignite/cli/decorators/TopologyDecorator.java
index af5426eb89..42d9ecea3e 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/commands/decorators/TopologyDecorator.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/decorators/TopologyDecorator.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.commands.decorators;
+package org.apache.ignite.cli.decorators;
 
 import com.jakewharton.fliptables.FlipTable;
 import java.util.List;
@@ -27,7 +27,6 @@ import org.apache.ignite.rest.client.model.ClusterNode;
  * Implementation of {@link Decorator} for the list of {@link ClusterNode}.
  */
 public class TopologyDecorator implements Decorator<List<ClusterNode>, TerminalOutput> {
-
     /**
      * Transform list of {@link ClusterNode} to {@link TerminalOutput}.
      *
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/deprecated/builtins/init/InitIgniteCommand.java b/modules/cli/src/main/java/org/apache/ignite/cli/deprecated/builtins/init/InitIgniteCommand.java
index 5240c84d9c..705a415970 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/deprecated/builtins/init/InitIgniteCommand.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/deprecated/builtins/init/InitIgniteCommand.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.cli.deprecated.builtins.init;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.ignite.cli.core.style.AnsiStringSupport.ansi;
 
 import jakarta.inject.Inject;
 import java.io.File;
@@ -31,14 +32,14 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.Optional;
 import java.util.Properties;
+import org.apache.ignite.cli.core.style.component.MessageUiComponent;
+import org.apache.ignite.cli.core.style.element.UiElements;
 import org.apache.ignite.cli.deprecated.CliPathsConfigLoader;
 import org.apache.ignite.cli.deprecated.IgniteCliException;
 import org.apache.ignite.cli.deprecated.IgnitePaths;
-import org.apache.ignite.cli.deprecated.Table;
 import org.apache.ignite.cli.deprecated.builtins.SystemPathResolver;
 import org.apache.ignite.cli.deprecated.builtins.module.ModuleManager;
 import org.jetbrains.annotations.NotNull;
-import picocli.CommandLine.Help.Ansi;
 import picocli.CommandLine.Help.ColorScheme;
 
 /**
@@ -62,8 +63,8 @@ public class InitIgniteCommand {
     /**
      * Creates init command instance.
      *
-     * @param pathRslvr      Resolver of paths like home directory and etc.
-     * @param moduleMgr      Manager of Ignite server and CLI modules.
+     * @param pathRslvr Resolver of paths like home directory and etc.
+     * @param moduleMgr Manager of Ignite server and CLI modules.
      * @param cliPathsCfgLdr Loader of current Ignite distro dirs configuration.
      */
     @Inject
@@ -81,8 +82,8 @@ public class InitIgniteCommand {
      * to recover after corruption of node directories structure.
      *
      * @param urls Urls with custom maven repositories for Ignite download.
-     * @param out  PrintWriter for output user message.
-     * @param cs   ColorScheme for enriching user outputs with colors.
+     * @param out PrintWriter for output user message.
+     * @param cs ColorScheme for enriching user outputs with colors.
      */
     public void init(URL[] urls, PrintWriter out, ColorScheme cs) {
         moduleMgr.setOut(out);
@@ -95,21 +96,13 @@ public class InitIgniteCommand {
 
         IgnitePaths cfg = cliPathsCfgLdr.loadIgnitePathsConfig().get();
 
-        out.print("Creating directories... ");
-
         cfg.initOrRecover();
 
-        out.println(Ansi.AUTO.string("@|green,bold Done!|@"));
-
-        Table tbl = new Table(0, cs);
-
-        tbl.addRow("@|bold Binaries Directory|@", cfg.binDir);
-        tbl.addRow("@|bold Work Directory|@", cfg.workDir);
-        tbl.addRow("@|bold Config Directory|@", cfg.cfgDir);
-        tbl.addRow("@|bold Log Directory|@", cfg.logDir);
-
-        out.println(tbl);
-        out.println();
+        out.println(cfg.binDir);
+        out.println(cfg.workDir);
+        out.println(cfg.cfgDir);
+        out.println(cfg.logDir);
+        out.println(ansi(UiElements.done().represent()));
 
         installIgnite(cfg, urls);
 
@@ -118,8 +111,13 @@ public class InitIgniteCommand {
         initJavaUtilLoggingPros(cfg.serverJavaUtilLoggingPros());
 
         out.println();
-        out.println("Apache Ignite is successfully initialized. Use the "
-                + cs.commandText("ignite node start") + " command to start a new local node.");
+        out.println(
+                MessageUiComponent.builder()
+                        .message("Apache Ignite is successfully initialized")
+                        .hint("Run the " + cs.commandText("ignite node start") + " command to start a new local node")
+                        .build()
+                        .render()
+        );
     }
 
     /**
@@ -160,7 +158,7 @@ public class InitIgniteCommand {
      * Downloads ignite node distro with all needed dependencies.
      *
      * @param ignitePaths Ignite distributive paths (bin, config, etc.).
-     * @param urls        Urls for custom maven repositories.
+     * @param urls Urls for custom maven repositories.
      */
     private void installIgnite(IgnitePaths ignitePaths, URL[] urls) {
         moduleMgr.addModule("_server", ignitePaths,
@@ -195,8 +193,8 @@ public class InitIgniteCommand {
     /**
      * Fills config file with bin and work directories paths.
      *
-     * @param f       Config file.
-     * @param binDir  Path for bin dir.
+     * @param f Config file.
+     * @param binDir Path for bin dir.
      * @param workDir Path for work dir.
      */
     private void fillNewConfigFile(File f,
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/deprecated/spec/NodeCommandSpec.java b/modules/cli/src/main/java/org/apache/ignite/cli/deprecated/spec/NodeCommandSpec.java
index 49416d3d79..7bfaf09c68 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/deprecated/spec/NodeCommandSpec.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/deprecated/spec/NodeCommandSpec.java
@@ -17,6 +17,9 @@
 
 package org.apache.ignite.cli.deprecated.spec;
 
+import static org.apache.ignite.cli.core.style.AnsiStringSupport.ansi;
+
+import com.jakewharton.fliptables.FlipTable;
 import com.typesafe.config.Config;
 import com.typesafe.config.ConfigFactory;
 import com.typesafe.config.ConfigRenderOptions;
@@ -31,10 +34,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import org.apache.ignite.cli.commands.BaseCommand;
+import org.apache.ignite.cli.core.style.element.UiElements;
 import org.apache.ignite.cli.deprecated.CliPathsConfigLoader;
 import org.apache.ignite.cli.deprecated.IgniteCliException;
 import org.apache.ignite.cli.deprecated.IgnitePaths;
-import org.apache.ignite.cli.deprecated.Table;
 import org.apache.ignite.cli.deprecated.builtins.node.NodeManager;
 import picocli.CommandLine.ArgGroup;
 import picocli.CommandLine.Command;
@@ -48,7 +51,7 @@ import picocli.CommandLine.Parameters;
  */
 @Command(
         name = "node",
-        description = "Manages locally running Ignite nodes.",
+        description = "Manages locally running Ignite nodes",
         subcommands = {
                 NodeCommandSpec.StartNodeCommandSpec.class,
                 NodeCommandSpec.StopNodeCommandSpec.class,
@@ -60,9 +63,12 @@ public class NodeCommandSpec {
     /**
      * Starts Ignite node command.
      */
-    @Command(name = "start", description = "Starts an Ignite node locally.")
+    @Command(name = "start", description = "Starts an Ignite node locally")
     @Singleton
     public static class StartNodeCommandSpec extends BaseCommand implements Callable<Integer> {
+        /** Consistent id, which will be used by new node. */
+        @Parameters(paramLabel = "name", description = "Name of the new node")
+        public String nodeName;
 
         /** Loader for Ignite distributive paths. */
         @Inject
@@ -72,10 +78,6 @@ public class NodeCommandSpec {
         @Inject
         private NodeManager nodeMgr;
 
-        /** Consistent id, which will be used by new node. */
-        @Parameters(paramLabel = "name", description = "Name of the new node")
-        public String nodeName;
-
         @ArgGroup(exclusive = false)
         private ConfigOptions configOptions;
 
@@ -117,18 +119,14 @@ public class NodeCommandSpec {
                     ignitePaths.serverJavaUtilLoggingPros(),
                     out);
 
+            out.println(ansi(UiElements.done().represent()));
+
+            out.println(String.format("[name: %s, pid: %d]", node.name, node.pid));
+
             out.println();
             out.println("Node is successfully started. To stop, type "
                     + cs.commandText("ignite node stop ") + cs.parameterText(node.name));
-            out.println();
-
-            Table tbl = new Table(0, cs);
 
-            tbl.addRow("@|bold Node name|@", node.name);
-            tbl.addRow("@|bold PID|@", node.pid);
-            tbl.addRow("@|bold Log File|@", node.logFile);
-
-            out.println(tbl);
             return 0;
         }
 
@@ -189,10 +187,10 @@ public class NodeCommandSpec {
             ColorScheme cs = spec.commandLine().getColorScheme();
 
             consistentIds.forEach(p -> {
-                out.print("Stopping locally running node with consistent ID " + cs.parameterText(p) + "... ");
+                out.println("Stopping locally running node with consistent ID " + cs.parameterText(p) + "...");
 
                 if (nodeMgr.stopWait(p, ignitePaths.cliPidsDir())) {
-                    out.println(cs.text("@|bold,green Done!|@"));
+                    out.println(cs.text("@|bold,green Done|@"));
                 } else {
                     out.println(cs.text("@|bold,red Failed|@"));
                 }
@@ -225,23 +223,22 @@ public class NodeCommandSpec {
             ColorScheme cs = spec.commandLine().getColorScheme();
 
             if (nodes.isEmpty()) {
-                out.println("Currently, there are no locally running nodes.");
-                out.println();
-                out.println("Use the " + cs.commandText("ignite node start")
-                        + " command to start a new node.");
+                out.println("There are no locally running nodes");
+                out.println("use the " + cs.commandText("ignite node start")
+                        + " command to start a new node");
             } else {
-                out.println("Number of running nodes: " + cs.text("@|bold " + nodes.size() + "|@"));
-                out.println();
+                String[] headers = {"consistent id", "pid", "log file"};
+                String[][] content = nodes.stream().map(
+                        node -> new String[]{
+                                node.name,
+                                String.valueOf(node.pid),
+                                String.valueOf(node.logFile)
+                        }
+                ).toArray(String[][]::new);
 
-                Table tbl = new Table(0, cs);
+                out.println(FlipTable.of(headers, content));
 
-                tbl.addRow("@|bold Consistent ID|@", "@|bold PID|@", "@|bold Log File|@");
-
-                for (NodeManager.RunningNode node : nodes) {
-                    tbl.addRow(node.name, node.pid, node.logFile);
-                }
-
-                out.println(tbl);
+                out.println("Number of running nodes: " + cs.text("@|bold " + nodes.size() + "|@"));
             }
             return 0;
         }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/deprecated/ui/ProgressBar.java b/modules/cli/src/main/java/org/apache/ignite/cli/deprecated/ui/ProgressBar.java
index 960d20b637..238771ca84 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/deprecated/ui/ProgressBar.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/deprecated/ui/ProgressBar.java
@@ -107,7 +107,7 @@ public class ProgressBar implements AutoCloseable {
 
         var completedPart = ((double) curr / (double) max);
 
-        // Space reserved for '||Done!'
+        // Space reserved for '||Done'
         var reservedSpace = 7;
 
         if (targetBarWidth < reservedSpace) {
@@ -133,7 +133,7 @@ public class ProgressBar implements AutoCloseable {
 
             sb.append("|").append(" ".repeat(4 - percentageLen)).append(percentage);
         } else {
-            sb.append("=|@|green,bold Done!|@");
+            sb.append("=|@|green,bold Done|@");
         }
 
         return Ansi.AUTO.string(sb.toString());
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/sql/SqlQueryResult.java b/modules/cli/src/main/java/org/apache/ignite/cli/sql/SqlQueryResult.java
index 45ad9a97f0..9337ecf386 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/sql/SqlQueryResult.java
+++ b/modules/cli/src/main/java/org/apache/ignite/cli/sql/SqlQueryResult.java
@@ -17,16 +17,15 @@
 
 package org.apache.ignite.cli.sql;
 
-import org.apache.ignite.cli.commands.decorators.TableDecorator;
 import org.apache.ignite.cli.core.decorator.Decorator;
 import org.apache.ignite.cli.core.decorator.TerminalOutput;
+import org.apache.ignite.cli.decorators.TableDecorator;
 import org.apache.ignite.cli.sql.table.Table;
 
 /**
  * Composite object of sql query result.
  */
 public class SqlQueryResult {
-
     private final Table<String> table;
 
     private final String message;
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/commands/UrlOptionsNegativeTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/commands/UrlOptionsNegativeTest.java
index a6307d4a1c..5926f51e59 100644
--- a/modules/cli/src/test/java/org/apache/ignite/cli/commands/UrlOptionsNegativeTest.java
+++ b/modules/cli/src/test/java/org/apache/ignite/cli/commands/UrlOptionsNegativeTest.java
@@ -164,7 +164,7 @@ public class UrlOptionsNegativeTest {
                 this::assertExitCodeIsFailure,
                 this::assertOutputIsEmpty,
                 () -> assertErrOutputIs(
-                        "Could not determine IP address when connecting to URL [url=http://no-such-host.com]" + System.lineSeparator())
+                        "Unknown host: http://no-such-host.com" + System.lineSeparator())
         );
     }
 
@@ -177,7 +177,8 @@ public class UrlOptionsNegativeTest {
         assertAll(
                 this::assertExitCodeIsFailure,
                 this::assertOutputIsEmpty,
-                () -> assertErrOutputIs("Could not connect to URL [url=" + NODE_URL + "]" + System.lineSeparator())
+                () -> assertErrOutputIs("Node unavailable" + System.lineSeparator()
+                        + "Could not connect to node with URL " + NODE_URL + System.lineSeparator())
         );
     }
 
@@ -213,8 +214,7 @@ public class UrlOptionsNegativeTest {
 
         assertAll(
                 this::assertOutputIsEmpty,
-                () -> assertErrOutputIs(
-                        "Could not determine IP address when connecting to URL [url=http://no-such-host.com]" + System.lineSeparator())
+                () -> assertErrOutputIs("Unknown host: http://no-such-host.com" + System.lineSeparator())
         );
     }
 
@@ -226,7 +226,8 @@ public class UrlOptionsNegativeTest {
 
         assertAll(
                 this::assertOutputIsEmpty,
-                () -> assertErrOutputIs("Could not connect to URL [url=" + NODE_URL + "]" + System.lineSeparator())
+                () -> assertErrOutputIs("Node unavailable" + System.lineSeparator()
+                        + "Could not connect to node with URL " + NODE_URL + System.lineSeparator())
         );
     }
 
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/commands/flow/FlowTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/commands/flow/FlowTest.java
index efe5194838..20896800b2 100644
--- a/modules/cli/src/test/java/org/apache/ignite/cli/commands/flow/FlowTest.java
+++ b/modules/cli/src/test/java/org/apache/ignite/cli/commands/flow/FlowTest.java
@@ -17,10 +17,16 @@
 
 package org.apache.ignite.cli.commands.flow;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.emptyString;
+import static org.hamcrest.Matchers.equalTo;
+
 import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
@@ -36,6 +42,7 @@ import org.jline.terminal.impl.DumbTerminal;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
@@ -92,6 +99,70 @@ class FlowTest {
         Assertions.assertEquals(1, call.value());
     }
 
+    @Test
+    @Disabled("https://issues.apache.org/jira/browse/IGNITE-17519")
+    void printsToOutput() throws IOException {
+        // Given
+        bindAnswers("no"); // we don't care about answer in this test
+
+        StringWriter out = new StringWriter();
+        PrintWriter output = new PrintWriter(out);
+        StringWriter errOut = new StringWriter();
+        PrintWriter errOutput = new PrintWriter(errOut);
+
+        // When build flow
+        Flow<Object, String> build = Flows.question("Do you like this?",
+                        List.of(new QuestionAnswer<>("yes"::equals, (a, i) -> 1),
+                                new QuestionAnswer<>("no"::equals, (a, i) -> 2))
+                )
+                .map(String::valueOf)
+                .exceptionHandler(new TestExceptionHandler())
+                .then(Flows.fromCall(new ThrowingStrCall(), StrCallInput::new))
+                .toOutput(output, errOutput)
+                .build();
+
+        // Then the output is empty
+        assertThat(errOut.toString(), emptyString());
+
+        // When start flow
+        build.start(Flowable.empty());
+
+        // Then output equals to the message from the exception because we use TestExceptionHandler
+        assertThat(errOut.toString(), equalTo("Ooops!")); // BUT there is the message taken from default exception handler
+    }
+
+    @Test
+    @Disabled("https://issues.apache.org/jira/browse/IGNITE-17519")
+    void printsToOutputThatWorks() throws IOException {
+        // Given
+        bindAnswers("no"); // we don't care about answer in this test
+
+        StringWriter out = new StringWriter();
+        PrintWriter output = new PrintWriter(out);
+        StringWriter errOut = new StringWriter();
+        PrintWriter errOutput = new PrintWriter(errOut);
+
+        // When build flow
+        Flow<Object, String> build = Flows.question("Do you like this?",
+                        List.of(new QuestionAnswer<>("yes"::equals, (a, i) -> 1),
+                                new QuestionAnswer<>("no"::equals, (a, i) -> 2))
+                )
+                .map(String::valueOf)
+                .toOutput(output, errOutput)
+                .then(Flows.fromCall(new ThrowingStrCall(), StrCallInput::new))
+                .exceptionHandler(new TestExceptionHandler())
+                .build();
+
+        // Then the output is empty
+        assertThat(errOut.toString(), emptyString());
+
+        // When start flow
+        build.start(Flowable.empty());
+
+        // Then output equals to the message from the exception because we use TestExceptionHandler
+        assertThat(errOut.toString(), equalTo("Ooops!")); // BUT it is empty
+    }
+
     private static Flow<Object, Integer> createFlow() {
         return Flows.question("Do you like this?",
                         List.of(new QuestionAnswer<>("yes"::equals, (a, i) -> 1),
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/IgniteCliExceptionHandler.java b/modules/cli/src/test/java/org/apache/ignite/cli/commands/flow/TestExceptionHandler.java
similarity index 69%
copy from modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/IgniteCliExceptionHandler.java
copy to modules/cli/src/test/java/org/apache/ignite/cli/commands/flow/TestExceptionHandler.java
index 0d98620803..4466a6d23d 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/core/exception/handler/IgniteCliExceptionHandler.java
+++ b/modules/cli/src/test/java/org/apache/ignite/cli/commands/flow/TestExceptionHandler.java
@@ -15,24 +15,22 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.core.exception.handler;
+package org.apache.ignite.cli.commands.flow;
 
+import jdk.jshell.spi.ExecutionControl.RunException;
 import org.apache.ignite.cli.core.exception.ExceptionHandler;
 import org.apache.ignite.cli.core.exception.ExceptionWriter;
-import org.apache.ignite.cli.deprecated.IgniteCliException;
 
-/**
- * Exception handler for {@link IgniteCliException}.
- */
-public class IgniteCliExceptionHandler implements ExceptionHandler<IgniteCliException> {
+class TestExceptionHandler implements ExceptionHandler<RunException> {
     @Override
-    public int handle(ExceptionWriter err, IgniteCliException e) {
+    public int handle(ExceptionWriter err, RunException e) {
         err.write(e.getMessage());
-        return 1;
+
+        return 0;
     }
 
     @Override
-    public Class<IgniteCliException> applicableException() {
-        return IgniteCliException.class;
+    public Class<RunException> applicableException() {
+        return RunException.class;
     }
 }
diff --git a/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/SectionAlreadyExistsException.java b/modules/cli/src/test/java/org/apache/ignite/cli/commands/flow/ThrowingStrCall.java
similarity index 66%
copy from modules/cli/src/main/java/org/apache/ignite/cli/config/ini/SectionAlreadyExistsException.java
copy to modules/cli/src/test/java/org/apache/ignite/cli/commands/flow/ThrowingStrCall.java
index 72b69ffe7e..3f872a0de5 100644
--- a/modules/cli/src/main/java/org/apache/ignite/cli/config/ini/SectionAlreadyExistsException.java
+++ b/modules/cli/src/test/java/org/apache/ignite/cli/commands/flow/ThrowingStrCall.java
@@ -15,13 +15,15 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.cli.config.ini;
+package org.apache.ignite.cli.commands.flow;
 
-/**
- * Exception when already created INI section trying to create.
- */
-public class SectionAlreadyExistsException extends RuntimeException {
-    public SectionAlreadyExistsException(String name) {
-        super("Section " + name + " already exists.");
+import org.apache.ignite.cli.core.call.Call;
+import org.apache.ignite.cli.core.call.CallOutput;
+import org.apache.ignite.cli.core.call.DefaultCallOutput;
+
+class ThrowingStrCall implements Call<StrCallInput, String> {
+    @Override
+    public CallOutput<String> execute(StrCallInput input) {
+        return DefaultCallOutput.failure(new RuntimeException("Ooops!"));
     }
 }
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/core/style/component/ErrorUiComponentTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/core/style/component/ErrorUiComponentTest.java
new file mode 100644
index 0000000000..7cf0af6d59
--- /dev/null
+++ b/modules/cli/src/test/java/org/apache/ignite/cli/core/style/component/ErrorUiComponentTest.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.cli.core.style.component;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+import java.util.UUID;
+import org.apache.ignite.cli.core.style.element.UiElements;
+import org.junit.jupiter.api.Test;
+
+class ErrorUiComponentTest {
+    @Test
+    void rendersHeader() {
+        // Given
+        ErrorUiComponent errorUiComponent = ErrorUiComponent.fromHeader("Just single header");
+
+        // When
+        String rendered = errorUiComponent.render();
+
+        // Then
+        assertThat(rendered, equalTo("Just single header"));
+    }
+
+    @Test
+    void rendersAllParts() {
+        // Given
+        ErrorUiComponent errorUiComponent = ErrorUiComponent.builder()
+                .header("I am header")
+                .errorCode("IGN-TBL-01")
+                .traceId(UUID.fromString("a0c5aca8-73ab-4e7e-bf7a-64fdcf08ece7"))
+                .details("Some useful information about the error happened")
+                .build();
+
+        // When
+        String rendered = errorUiComponent.render();
+
+        // Then
+        assertThat(rendered, equalTo(
+                "IGN-TBL-01 Trace ID: a0c5aca8-73ab-4e7e-bf7a-64fdcf08ece7" + System.lineSeparator()
+                + "I am header" + System.lineSeparator()
+                + "Some useful information about the error happened"
+        ));
+    }
+
+    @Test
+    void renderAllPartsWithElements() {
+        // Given
+        ErrorUiComponent errorUiComponent = ErrorUiComponent.builder()
+                .header("I am header with url %s", UiElements.url("http://host.com"))
+                .errorCode("IGN-TBL-01")
+                .traceId(UUID.fromString("a0c5aca8-73ab-4e7e-bf7a-64fdcf08ece7"))
+                .details("Some useful information about the error happened with url %s", UiElements.url("http://host.com"))
+                .build();
+
+        // When
+        String rendered = errorUiComponent.render();
+
+        // Then
+        assertThat(rendered, equalTo(
+                "IGN-TBL-01 Trace ID: a0c5aca8-73ab-4e7e-bf7a-64fdcf08ece7" + System.lineSeparator()
+                        + "I am header with url http://host.com" + System.lineSeparator()
+                        + "Some useful information about the error happened with url http://host.com"
+        ));
+    }
+}
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/core/style/component/MessageUiComponentTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/core/style/component/MessageUiComponentTest.java
new file mode 100644
index 0000000000..c8a5d8fb07
--- /dev/null
+++ b/modules/cli/src/test/java/org/apache/ignite/cli/core/style/component/MessageUiComponentTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli.core.style.component;
+
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+import org.apache.ignite.cli.core.style.element.UiElements;
+import org.junit.jupiter.api.Test;
+
+class MessageUiComponentTest {
+    @Test
+    void rendersMessageAndHint() {
+        // Given
+        var messageUiComponent = MessageUiComponent.builder()
+                .message("Hello, I am a message")
+                .hint("And I am a hint")
+                .build();
+
+        // When
+        String renderedUiComponent = messageUiComponent.render();
+
+        // Then
+        assertThat(renderedUiComponent, equalTo("Hello, I am a message" + System.lineSeparator() + "And I am a hint"));
+    }
+
+    @Test
+    void rendersMessage() {
+        // Given message without a hint
+        var messageUiComponent = MessageUiComponent.fromMessage("Hello, I am just a message");
+
+        // When
+        String renderedUiComponent = messageUiComponent.render();
+
+        // Then there is no \n at the end
+        assertThat(renderedUiComponent, equalTo("Hello, I am just a message"));
+    }
+
+    @Test
+    void rendersMessageWithUrl() {
+        // Given
+        MessageUiComponent messageUiComponent = MessageUiComponent.fromMessage(
+                "This is a message with url %s",
+                UiElements.url("https://host.com")
+        );
+
+        // When
+        String renderedUiComponent = messageUiComponent.render();
+
+        // Then
+        assertThat(renderedUiComponent, equalTo("This is a message with url https://host.com"));
+    }
+}
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/deprecated/IgniteCliInterfaceTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/deprecated/IgniteCliInterfaceTest.java
index a4f5337e8e..e3d67984d4 100644
--- a/modules/cli/src/test/java/org/apache/ignite/cli/deprecated/IgniteCliInterfaceTest.java
+++ b/modules/cli/src/test/java/org/apache/ignite/cli/deprecated/IgniteCliInterfaceTest.java
@@ -203,14 +203,9 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
                     ignitePaths.serverJavaUtilLoggingPros(),
                     cli.getOut());
 
-            assertOutputEqual("\nNode is successfully started. To stop, type ignite node stop " + nodeName + "\n\n"
-                            + "+-----------+---------+\n"
-                            + "| Node name | node1   |\n"
-                            + "+-----------+---------+\n"
-                            + "| PID       | 1       |\n"
-                            + "+-----------+---------+\n"
-                            + "| Log File  | logfile |\n"
-                            + "+-----------+---------+\n"
+            assertOutputEqual("Done\n"
+                    + "[name: " + nodeName + ", pid: 1]\n\n"
+                            + "Node is successfully started. To stop, type ignite node stop node1"
             );
             assertThatStderrIsEmpty();
         }
@@ -343,7 +338,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
             assertOutputEqual(
                     "Stopping locally running node with consistent ID "
                             + cmd.getColorScheme().parameterText(nodeName)
-                            + cmd.getColorScheme().text("... @|bold,green Done!|@\n")
+                            + cmd.getColorScheme().text("...\n@|bold,green Done|@\n")
             );
             assertThatStderrIsEmpty();
         }
@@ -368,7 +363,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
             assertOutputEqual(
                     "Stopping locally running node with consistent ID "
                             + cmd.getColorScheme().parameterText(nodeName)
-                            + cmd.getColorScheme().text("... @|bold,red Failed|@\n")
+                            + cmd.getColorScheme().text("...\n@|bold,red Failed|@\n")
             );
             assertThatStderrIsEmpty();
         }
@@ -391,14 +386,15 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
 
             assertThatExitCodeMeansSuccess(exitCode);
             verify(nodeMgr).getRunningNodes(ignitePaths.logDir, ignitePaths.cliPidsDir());
-            assertOutputEqual(cmd.getColorScheme().text("Number of running nodes: @|bold 2|@\n\n")
-                    + "+---------------+-----+----------+\n"
-                    + cmd.getColorScheme().text("| @|bold Consistent ID|@ | @|bold PID|@ | @|bold Log File|@ |\n")
-                    + "+---------------+-----+----------+\n"
-                    + "| new1          | 1   | logFile1 |\n"
-                    + "+---------------+-----+----------+\n"
-                    + "| new2          | 2   | logFile2 |\n"
-                    + "+---------------+-----+----------+\n"
+            assertOutputEqual(
+                    "╔═══════════════╤═════╤══════════╗\n"
+                    + "║ consistent id │ pid │ log file ║\n"
+                    + "╠═══════════════╪═════╪══════════╣\n"
+                    + "║ new1          │ 1   │ logFile1 ║\n"
+                    + "╟───────────────┼─────┼──────────╢\n"
+                    + "║ new2          │ 2   │ logFile2 ║\n"
+                    + "╚═══════════════╧═════╧══════════╝\n\n"
+                    + cmd.getColorScheme().text("Number of running nodes: @|bold 2|@\n")
             );
             assertThatStderrIsEmpty();
         }
@@ -418,8 +414,8 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
 
             assertThatExitCodeMeansSuccess(exitCode);
             verify(nodeMgr).getRunningNodes(ignitePaths.logDir, ignitePaths.cliPidsDir());
-            assertOutputEqual("Currently, there are no locally running nodes.\n\n"
-                            + "Use the " + cmd.getColorScheme().commandText("ignite node start") + " command to start a new node.\n"
+            assertOutputEqual("There are no locally running nodes\n"
+                            + "use the " + cmd.getColorScheme().commandText("ignite node start") + " command to start a new node"
             );
             assertThatStderrIsEmpty();
         }
@@ -508,7 +504,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
 
                 assertThatExitCodeMeansSuccess(exitCode);
 
-                assertOutputEqual("Node configuration was updated successfully.");
+                assertOutputEqual("Node configuration was updated successfully");
                 assertThatStderrIsEmpty();
             }
         }
@@ -550,7 +546,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
 
             assertThatExitCodeMeansSuccess(exitCode);
 
-            assertOutputEqual("Cluster was initialized successfully.");
+            assertOutputEqual("Cluster was initialized successfully");
             assertThatStderrIsEmpty();
         }
 
@@ -563,7 +559,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
                     )
                     .respond(response()
                             .withStatusCode(INTERNAL_SERVER_ERROR_500.code())
-                            .withBody("Oops")
+                            .withBody("{\"status\":500, \"detail\":\"Oops\"}")
                     );
 
             int exitCode = cmd(ctx).execute(
@@ -579,7 +575,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
             assertThatExitCodeIs(1, exitCode);
 
             assertThatStdoutIsEmpty();
-            assertErrOutputEqual("An error occurred [errorCode=500, response=Oops]");
+            assertErrOutputEqual("Oops");
         }
 
         @Test
@@ -619,7 +615,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
 
             assertThatExitCodeMeansSuccess(exitCode);
 
-            assertOutputEqual("Cluster was initialized successfully.");
+            assertOutputEqual("Cluster was initialized successfully");
             assertThatStderrIsEmpty();
         }
 
@@ -702,7 +698,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest {
 
                 assertThatExitCodeMeansSuccess(exitCode);
 
-                assertOutputEqual("Cluster configuration was updated successfully.");
+                assertOutputEqual("Cluster configuration was updated successfully");
                 assertThatStderrIsEmpty();
             }
         }
diff --git a/modules/cli/src/test/java/org/apache/ignite/cli/deprecated/ui/ProgressBarTest.java b/modules/cli/src/test/java/org/apache/ignite/cli/deprecated/ui/ProgressBarTest.java
index d678d1c824..93355b8932 100644
--- a/modules/cli/src/test/java/org/apache/ignite/cli/deprecated/ui/ProgressBarTest.java
+++ b/modules/cli/src/test/java/org/apache/ignite/cli/deprecated/ui/ProgressBarTest.java
@@ -72,8 +72,8 @@ public class ProgressBarTest extends AbstractCliTest {
         assertEquals(AUTO.string(
                         "\r|========================>                                                 | 33%"
                                 + "\r|================================================>                         | 66%"
-                                + "\r|==========================================================================|@|green,bold Done!|@"
-                                + "\r|==========================================================================|@|green,bold Done!|@"),
+                                + "\r|==========================================================================|@|green,bold Done|@"
+                                + "\r|==========================================================================|@|green,bold Done|@"),
                 outputStream.toString(UTF_8)
         );
     }
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
index 097944e6d6..5547d8390c 100644
--- a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/validation/ConfigurationValidationException.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.configuration.validation;
 
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Configuration validation exception.
@@ -41,7 +42,7 @@ public class ConfigurationValidationException extends RuntimeException {
      * @param issues List of issues occurred during validation.
      */
     public ConfigurationValidationException(List<ValidationIssue> issues) {
-        super(issues.toString());
+        super(createMessageFromIssues(issues));
 
         this.issues = issues;
     }
@@ -54,4 +55,9 @@ public class ConfigurationValidationException extends RuntimeException {
     public List<ValidationIssue> getIssues() {
         return issues;
     }
+
+    private static String createMessageFromIssues(List<ValidationIssue> issues) {
+        return "Validation did not pass for keys: "
+                + issues.stream().map(issue -> "[" + issue.key() + ", " + issue.message() + "]").collect(Collectors.joining(", "));
+    }
 }
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
index 80d04b5818..be5359a10b 100644
--- 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
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.rest.configuration;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasSize;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
@@ -35,8 +36,8 @@ 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.InvalidParam;
 import org.apache.ignite.internal.rest.api.Problem;
-import org.apache.ignite.internal.rest.api.ValidationProblem;
 import org.jetbrains.annotations.NotNull;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -136,19 +137,18 @@ public abstract class ConfigurationControllerBaseTest {
 
         assertEquals(HttpStatus.BAD_REQUEST, thrown.getResponse().status());
 
-        var problem = getValidationProblem(thrown);
+        var problem = getProblem(thrown);
         assertEquals(400, problem.status());
-        assertThat(problem.detail(), containsString("ValidationIssue [key=root.foo, message=Error word]"));
-        assertEquals("Error word", problem.invalidParams().stream().findFirst().get().reason()); // todo: check name and reason
+        assertThat(problem.detail(), containsString("Validation did not pass for keys: [root.foo, Error word]"));
+        assertThat(problem.invalidParams(), hasSize(1));
+
+        InvalidParam invalidParam = problem.invalidParams().stream().findFirst().get();
+        assertEquals("root.foo", invalidParam.name());
+        assertEquals("Error word", invalidParam.reason());
     }
 
     @NotNull
     private Problem getProblem(HttpClientResponseException exception) {
         return exception.getResponse().getBody(Problem.class).orElseThrow();
     }
-
-    @NotNull
-    private ValidationProblem getValidationProblem(HttpClientResponseException exception) {
-        return exception.getResponse().getBody(ValidationProblem.class).orElseThrow();
-    }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroup.java b/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroup.java
index c9a97da9fb..3f18678b13 100755
--- a/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroup.java
+++ b/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroup.java
@@ -229,14 +229,29 @@ public class ErrorGroup {
         String c = (cause != null && cause.getMessage() != null) ? cause.getMessage() : null;
 
         if (c != null) {
-            Matcher m = EXCEPTION_MESSAGE_PATTERN.matcher(c);
-
-            c = (m.matches()) ? m.group(8) : c;
+            c = extractCauseMessage(c);
         }
 
         return errorMessage(traceId, groupName, code, c);
     }
 
+    /**
+     * Returns a message extracted from the given {@code errorMessage} if this {@code errorMessage} matches
+     * {@link #EXCEPTION_MESSAGE_PATTERN}. If {@code errorMessage} does not match the pattern or {@code null} then returns the original
+     * {@code errorMessage}.
+     *
+     * @param errorMessage Message that is returned by {@link Throwable#getMessage()}
+     * @return Extracted message.
+     */
+    public static String extractCauseMessage(String errorMessage) {
+        if (errorMessage == null) {
+            return null;
+        }
+
+        Matcher m = EXCEPTION_MESSAGE_PATTERN.matcher(errorMessage);
+        return (m.matches()) ? m.group(8) : errorMessage;
+    }
+
     /** {@inheritDoc} */
     @Override
     public String toString() {
diff --git a/modules/core/src/test/java/org/apache/ignite/lang/ErrorGroupTest.java b/modules/core/src/test/java/org/apache/ignite/lang/ErrorGroupTest.java
new file mode 100644
index 0000000000..65e95429f5
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/lang/ErrorGroupTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.lang;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+import org.junit.jupiter.api.Test;
+
+class ErrorGroupTest {
+    @Test
+    void extractsCauseMessageFromIgniteExceptionMessage() {
+        // Given
+        String igniteExceptionMessage = "IGN-CMN-65535 TraceId:24103638-d079-4a19-a8f6-ca9c23662908 I'm the reason";
+
+        // When
+        String extractedMessage = ErrorGroup.extractCauseMessage(igniteExceptionMessage);
+
+        // Then
+        assertThat(extractedMessage, equalTo("I'm the reason"));
+    }
+
+    @Test
+    void extractsEmptyCauseMessageFromIgniteExceptionMessage() {
+        // Given message without the reason of the error
+        String igniteExceptionMessage = "IGN-CMN-65535 TraceId:24103638-d079-4a19-a8f6-ca9c23662908";
+
+        // When
+        String extractedMessage = ErrorGroup.extractCauseMessage(igniteExceptionMessage);
+
+        // Then
+        assertThat(extractedMessage, equalTo(""));
+    }
+}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Problem.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Problem.java
index d0e814e7b7..e7c9bec2c2 100644
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Problem.java
+++ b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/Problem.java
@@ -20,6 +20,7 @@ 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;
+import java.util.Collection;
 import java.util.Objects;
 import java.util.UUID;
 import org.apache.ignite.internal.rest.constants.HttpCode;
@@ -50,6 +51,9 @@ public class Problem {
     /** Unique identifier that will help to trace the error in the log (optional). */
     private final UUID traceId;
 
+    /** List of parameter that did not pass the validation (optional). */
+    private final Collection<InvalidParam> invalidParams;
+
     /** Constructor. */
     @JsonCreator
     protected Problem(
@@ -59,7 +63,8 @@ public class Problem {
             @JsonProperty("type") String type,
             @JsonProperty("detail") String detail,
             @JsonProperty("node") String node,
-            @JsonProperty("traceId") UUID traceId) {
+            @JsonProperty("traceId") UUID traceId,
+            @JsonProperty("invalidParams") Collection<InvalidParam> invalidParams) {
         this.title = title;
         this.status = status;
         this.code = code;
@@ -67,6 +72,7 @@ public class Problem {
         this.detail = detail;
         this.node = node;
         this.traceId = traceId;
+        this.invalidParams = invalidParams;
     }
 
     /** Returns {@link ProblemBuilder}. */
@@ -118,6 +124,11 @@ public class Problem {
         return traceId;
     }
 
+    @JsonGetter("invalidParams")
+    public Collection<InvalidParam> invalidParams() {
+        return invalidParams;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
@@ -146,6 +157,7 @@ public class Problem {
                 + ", type='" + type + '\''
                 + ", detail='" + detail + '\''
                 + ", node='" + node + '\''
+                + ", invalidParams='" + invalidParams + '\''
                 + ", traceId=" + traceId
                 + '}';
     }
@@ -166,6 +178,8 @@ public class Problem {
 
         protected UUID traceId;
 
+        protected Collection<InvalidParam> invalidParams;
+
         public B title(String title) {
             this.title = title;
             return (B) this;
@@ -201,9 +215,14 @@ public class Problem {
             return (B) this;
         }
 
+        public B invalidParams(Collection<InvalidParam> invalidParams) {
+            this.invalidParams = invalidParams;
+            return (B) this;
+        }
+
         @Override
         public T build() {
-            return (T) new Problem(title, status, code, type, detail, node, traceId);
+            return (T) new Problem(title, status, code, type, detail, node, traceId, invalidParams);
         }
     }
 }
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/ValidationProblem.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/ValidationProblem.java
deleted file mode 100644
index c6964671c1..0000000000
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/ValidationProblem.java
+++ /dev/null
@@ -1,111 +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.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonGetter;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import java.util.Collection;
-import java.util.Objects;
-import java.util.UUID;
-import org.apache.ignite.internal.rest.constants.HttpCode;
-
-/**
- * Validation problem that adds one more property (invalidParams) to the standard problem.
- */
-public class ValidationProblem extends Problem {
-    /** List of parameter that did not pass the validation (optional). */
-    private final Collection<InvalidParam> invalidParams;
-
-    /** Constructor. */
-    @JsonCreator
-    public ValidationProblem(
-            @JsonProperty("title") String title,
-            @JsonProperty("status") int status,
-            @JsonProperty("code") String code,
-            @JsonProperty("type") String type,
-            @JsonProperty("detail") String detail,
-            @JsonProperty("node") String node,
-            @JsonProperty("traceId") UUID traceId,
-            @JsonProperty("invalidParams") Collection<InvalidParam> invalidParams) {
-
-        super(title, status, code, type, detail, node, traceId);
-        this.invalidParams = invalidParams;
-    }
-
-    @JsonGetter("invalidParams")
-    public Collection<InvalidParam> invalidParams() {
-        return invalidParams;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        if (!super.equals(o)) {
-            return false;
-        }
-        ValidationProblem that = (ValidationProblem) o;
-        return Objects.equals(invalidParams, that.invalidParams);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(super.hashCode(), invalidParams);
-    }
-
-    @Override
-    public String toString() {
-        return "ValidationProblem{"
-                + "invalidParams=" + invalidParams
-                + "} " + super.toString();
-    }
-
-    /** Returns {@link ValidationProblemBuilder}. */
-    public static ValidationProblemBuilder builder() {
-        return new ValidationProblemBuilder();
-    }
-
-    /** Returns {@link ValidationProblemBuilder} with http status and title. */
-    public static ValidationProblemBuilder fromHttpCode(HttpCode httpCode) {
-        ValidationProblemBuilder builder = new ValidationProblemBuilder();
-        builder.status(httpCode.code());
-        builder.title(httpCode.message());
-
-        return builder;
-    }
-
-    /** Builder for {@link ValidationProblem}. */
-    public static class ValidationProblemBuilder extends ProblemBuilder<ValidationProblem, ValidationProblemBuilder> {
-        private Collection<InvalidParam> invalidParams;
-
-        public ValidationProblemBuilder invalidParams(Collection<InvalidParam> invalidParams) {
-            this.invalidParams = invalidParams;
-            return this;
-        }
-
-        @Override
-        public ValidationProblem build() {
-            return new ValidationProblem(title, status, code, type, detail, node, traceId, invalidParams);
-        }
-    }
-}
diff --git a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/exception/handler/IgniteExceptionHandler.java b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/exception/handler/IgniteExceptionHandler.java
index 342e79a6eb..9f14a3297b 100644
--- a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/exception/handler/IgniteExceptionHandler.java
+++ b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/exception/handler/IgniteExceptionHandler.java
@@ -27,10 +27,11 @@ import java.util.stream.Collectors;
 import org.apache.ignite.configuration.validation.ConfigurationValidationException;
 import org.apache.ignite.internal.rest.api.InvalidParam;
 import org.apache.ignite.internal.rest.api.Problem;
-import org.apache.ignite.internal.rest.api.ValidationProblem;
 import org.apache.ignite.internal.rest.constants.HttpCode;
 import org.apache.ignite.internal.rest.problem.HttpProblemResponse;
+import org.apache.ignite.lang.ErrorGroup;
 import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Handles {@link IgniteException} and represents it as an application/problem+json response.
@@ -40,10 +41,12 @@ import org.apache.ignite.lang.IgniteException;
 public class IgniteExceptionHandler implements ExceptionHandler<IgniteException, HttpResponse<? extends Problem>> {
     @Override
     public HttpResponse<? extends Problem> handle(HttpRequest request, IgniteException exception) {
+        String detail = extractDetailMessageOrNull(exception);
+
         if (exception.getCause() instanceof IllegalArgumentException) {
             return HttpProblemResponse.from(
                     Problem.fromHttpCode(HttpCode.BAD_REQUEST)
-                            .detail(exception.getMessage())
+                            .detail(detail)
                             .traceId(exception.traceId())
                             .code(exception.codeAsString())
             );
@@ -51,8 +54,8 @@ public class IgniteExceptionHandler implements ExceptionHandler<IgniteException,
 
         if (exception.getCause() instanceof ConfigurationValidationException) {
             return HttpProblemResponse.from(
-                    ValidationProblem.fromHttpCode(HttpCode.BAD_REQUEST)
-                            .detail(exception.getMessage())
+                    Problem.fromHttpCode(HttpCode.BAD_REQUEST)
+                            .detail(detail)
                             .invalidParams(mapValidationIssuesToRestFormat((ConfigurationValidationException) exception.getCause()))
                             .traceId(exception.traceId())
                             .code(exception.codeAsString())
@@ -61,12 +64,21 @@ public class IgniteExceptionHandler implements ExceptionHandler<IgniteException,
 
         return HttpProblemResponse.from(
                 Problem.fromHttpCode(HttpCode.INTERNAL_ERROR)
-                        .detail(exception.getMessage())
+                        .detail(detail)
                         .traceId(exception.traceId())
                         .code(exception.codeAsString())
         );
     }
 
+    @Nullable
+    private static String extractDetailMessageOrNull(IgniteException exception) {
+        String detail = ErrorGroup.extractCauseMessage(exception.getMessage());
+        if (detail != null && detail.isBlank()) {
+            detail = null;
+        }
+        return detail;
+    }
+
     private List<InvalidParam> mapValidationIssuesToRestFormat(ConfigurationValidationException exception) {
         return exception.getIssues()
                 .stream()
diff --git a/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/exception/handler/IgniteExceptionHandlerTest.java b/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/exception/handler/IgniteExceptionHandlerTest.java
index c666799f56..5344997de9 100644
--- a/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/exception/handler/IgniteExceptionHandlerTest.java
+++ b/modules/rest-api/src/test/java/org/apache/ignite/internal/rest/exception/handler/IgniteExceptionHandlerTest.java
@@ -17,7 +17,6 @@
 
 package org.apache.ignite.internal.rest.exception.handler;
 
-import static org.apache.ignite.lang.ErrorGroup.errorMessage;
 import static org.apache.ignite.lang.ErrorGroup.extractErrorCode;
 import static org.apache.ignite.lang.ErrorGroups.Common.COMMON_ERR_GROUP;
 import static org.apache.ignite.lang.ErrorGroups.Common.UNKNOWN_ERR;
@@ -34,7 +33,6 @@ import org.apache.ignite.configuration.validation.ValidationIssue;
 import org.apache.ignite.internal.rest.api.InvalidParam;
 import org.apache.ignite.internal.rest.api.Problem;
 import org.apache.ignite.internal.rest.api.Problem.ProblemBuilder;
-import org.apache.ignite.internal.rest.api.ValidationProblem;
 import org.apache.ignite.lang.ErrorGroup;
 import org.apache.ignite.lang.IgniteException;
 import org.junit.jupiter.api.BeforeEach;
@@ -49,7 +47,6 @@ class IgniteExceptionHandlerTest {
 
     static Stream<Arguments> igniteExceptions() {
         UUID traceId = UUID.randomUUID();
-        String errorMessage = errorMessage(traceId, UNKNOWN_ERR, null);
         String humanReadableCode = ErrorGroup.ERR_PREFIX + COMMON_ERR_GROUP.name() + '-' + extractErrorCode(UNKNOWN_ERR);
 
         var invalidParams = List.of(
@@ -69,7 +66,7 @@ class IgniteExceptionHandlerTest {
                                 .status(500)
                                 .title("Internal Server Error")
                                 .code(humanReadableCode)
-                                .detail(errorMessage + " Ooops")
+                                .detail("Ooops")
                                 .traceId(traceId)),
                 Arguments.of(
                         // given
@@ -79,7 +76,6 @@ class IgniteExceptionHandlerTest {
                                 .status(500)
                                 .title("Internal Server Error")
                                 .code(humanReadableCode)
-                                .detail(errorMessage)
                                 .traceId(traceId)),
                 Arguments.of(
                         // given
@@ -90,7 +86,7 @@ class IgniteExceptionHandlerTest {
                                 .title("Bad Request")
                                 .code(humanReadableCode)
                                 .traceId(traceId)
-                                .detail(errorMessage + " Illegal value")),
+                                .detail("Illegal value")),
                 Arguments.of(
                         // given
                         new IgniteException(
@@ -98,10 +94,10 @@ class IgniteExceptionHandlerTest {
                                 UNKNOWN_ERR,
                                 new ConfigurationValidationException(validationIssues)),
                         // expected
-                        ValidationProblem.builder()
+                        Problem.builder()
                                 .status(400)
                                 .title("Bad Request")
-                                .detail(errorMessage + ' ' + validationIssues)
+                                .detail("Validation did not pass for keys: [key1, Some issue1], [key2, Some issue2]")
                                 .code(humanReadableCode)
                                 .traceId(traceId)
                                 .invalidParams(invalidParams))
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java
index be2635546e..9f50330ebe 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/rest/ItInitializedClusterRestTest.java
@@ -139,6 +139,21 @@ public class ItInitializedClusterRestTest extends AbstractRestTestBase {
         assertThat(config.getInt("rocksDb.defaultRegion.writeBufferSize"), is(1024));
     }
 
+    @Test
+    @DisplayName("Cluster configuration can not be updated if provided config did not pass the validation")
+    void clusterConfigurationUpdateValidation() throws IOException, InterruptedException {
+        // When PATCH /management/v1/configuration/cluster invalid with invalid value
+        HttpResponse<String> patchRequest = client.send(
+                patch("/management/v1/configuration/cluster", "rocksDb.defaultRegion.cache=invalid"),
+                BodyHandlers.ofString()
+        );
+
+        // Then
+        assertThat(patchRequest.statusCode(), is(400));
+        // And invalidParams key is in response body
+        assertThat(patchRequest.body(), hasJsonPath("$.invalidParams"));
+    }
+
     @Test
     @DisplayName("Cluster configuration by path is available when the cluster is initialized")
     void clusterConfigurationByPath() throws IOException, InterruptedException {