You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2025/11/07 12:05:27 UTC

(syncope) branch master updated: [SYNCOPE-1928] Implementing self-refresh behavior via WebSocket (#1231)

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

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new 97e022a5b9  [SYNCOPE-1928] Implementing self-refresh behavior via WebSocket  (#1231)
97e022a5b9 is described below

commit 97e022a5b9b533a2be6ceeb848d6412a18ddc806
Author: Francesco Chicchiriccò <il...@users.noreply.github.com>
AuthorDate: Fri Nov 7 13:03:07 2025 +0100

     [SYNCOPE-1928] Implementing self-refresh behavior via WebSocket  (#1231)
---
 .github/workflows/oracle.yml                       |   2 +
 .mvn/extensions.xml                                |   2 +-
 .../console/panels/AMSessionDirectoryPanel.java    |   5 +-
 .../console/panels/LinkedAccountModalPanel.java    |   4 +-
 .../console/panels/RemediationDirectoryPanel.java  |   3 +-
 .../policies/ProvisioningPolicyModalPanel.java     |  27 +++---
 .../console/status/AnyStatusDirectoryPanel.java    |   6 +-
 .../syncope/client/console/topology/Topology.java  |  27 +++---
 .../console/topology/TopologyTogglePanel.java      |   6 +-
 .../client/console/widgets/RemediationsWidget.java |  73 +--------------
 .../wizards/any/LinkedAccountWizardBuilder.java    |   3 +-
 .../any/MergeLinkedAccountsSearchPanel.java        |  11 +--
 .../syncope/client/ui/commons/BaseLogin.java       |  10 --
 .../ui/commons/SyncopeUIRequestCycleListener.java  |  44 ++++-----
 .../ui/commons/wizards/any/AbstractGroups.java     |   9 +-
 client/idrepo/console/pom.xml                      |  18 +++-
 .../client/console/IdRepoConsoleContext.java       |  37 ++++++++
 ...erBehavior.java => SessionTimeoutListener.java} |  26 ++----
 .../client/console/SyncopeConsoleApplication.java  |  15 ++-
 .../client/console/SyncopeConsoleSession.java      |  21 +++--
 .../client/console/SyncopeWebApplication.java      |  57 +++++++++--
 .../syncope/client/console/batch/BatchContent.java |  49 ++++------
 .../init/ClassPathScanImplementationLookup.java    |   4 +-
 .../syncope/client/console/pages/BasePage.java     |  10 +-
 .../syncope/client/console/pages/Realms.java       |   9 +-
 .../syncope/client/console/panels/AnyPanel.java    |  16 ++--
 .../console/panels/CommandDirectoryPanel.java      |   4 +-
 .../console/panels/DashboardOverviewPanel.java     |  30 +++---
 .../syncope/client/console/panels/LogsPanel.java   |   5 +-
 .../client/console/panels/SchemaTypePanel.java     |   4 +-
 .../console/reports/ReportDirectoryPanel.java      |  17 ++--
 .../tasks/ProvisioningTaskDirectoryPanel.java      |  13 ++-
 .../console/tasks/SchedTaskDirectoryPanel.java     |  13 ++-
 .../client/console/tasks/TaskDirectoryPanel.java   |   5 +-
 .../markup/html/form/ActionLinksTogglePanel.java   |   4 +-
 .../wicket/ws/RefreshWebSocketBehavior.java        |  77 +++++++++++++++
 .../client/console/widgets/AlertWidget.java        |  29 +-----
 .../client/console/widgets/ExtAlertWidget.java     |  22 ++++-
 .../console/widgets/ItemTransformerWidget.java     |  18 +---
 .../console/widgets/JEXLTransformerWidget.java     |  35 ++-----
 .../syncope/client/console/widgets/JobWidget.java  |  15 ++-
 .../console/wizards/UserSelectionWizardStep.java   |  42 ++++-----
 .../client/console/wizards/WizardMgtPanel.java     |   5 +-
 .../any/AnyObjectTemplateWizardBuilder.java        |   5 +-
 .../console/wizards/any/AnyWizardBuilder.java      |  17 ++--
 .../client/console/wizards/any/Details.java        |   8 +-
 .../wizards/any/GroupTemplateWizardBuilder.java    |   5 +-
 .../console/wizards/any/GroupWizardBuilder.java    |   4 +-
 .../client/console/wizards/any/Ownership.java      |   6 +-
 .../client/console/wizards/any/PlainAttrs.java     |  22 ++---
 .../client/console/wizards/any/Relationships.java  |  23 ++---
 .../wizards/any/UserTemplateWizardBuilder.java     |   5 +-
 .../console/wizards/any/UserWizardBuilder.java     |   4 +-
 .../apache/syncope/client/console/pages/Login.html |   1 -
 .../client/console/widgets/AlertWidget.html        |   1 -
 .../syncope/client/console/AbstractTest.java       |  16 +++-
 .../SyncopeEnduserRequestCycleListener.java        | 104 ---------------------
 .../client/enduser/SyncopeWebApplication.java      |   7 +-
 .../enduser/pages/SelfConfirmPasswordReset.java    |   4 +-
 .../client/enduser/panels/UserSelfFormPanel.java   |   4 +-
 .../syncope/client/enduser/panels/any/Groups.java  |   9 +-
 .../client/enduser/panels/any/Resources.java       |  32 +++----
 .../apache/syncope/client/enduser/pages/Login.html |   1 -
 .../api/data/AccessTokenDataBinder.java            |   3 -
 .../java/data/AccessTokenDataBinderImpl.java       |   5 +-
 .../console/panels/UserRequestDirectoryPanel.java  |   3 +-
 .../panels/UserRequestFormDirectoryPanel.java      |   7 +-
 .../console/widgets/UserRequestFormsWidget.java    |  81 +++-------------
 .../client/console/pages/OIDCClientLogin.java      |  16 ++--
 .../client/console/pages/OIDCClientSelfReg.java    |   9 +-
 .../panels/OIDCProvidersDirectoryPanel.java        |   3 +-
 .../client/enduser/pages/OIDCClientLogin.java      |  16 ++--
 .../syncope/client/console/pages/SAML2SPLogin.java |  16 ++--
 .../client/console/pages/SAML2SPSelfReg.java       |  11 ++-
 .../console/panels/SAML2IdPsDirectoryPanel.java    |   3 +-
 .../syncope/client/enduser/pages/SAML2SPLogin.java |  16 ++--
 fit/core-reference/pom.xml                         |   5 +
 .../syncope/fit/console/AbstractConsoleITCase.java |  13 +++
 .../syncope/fit/core/NotificationTaskITCase.java   |   6 +-
 pom.xml                                            |   4 +-
 80 files changed, 615 insertions(+), 712 deletions(-)

diff --git a/.github/workflows/oracle.yml b/.github/workflows/oracle.yml
index e14dc66073..a30c49ab00 100644
--- a/.github/workflows/oracle.yml
+++ b/.github/workflows/oracle.yml
@@ -28,6 +28,8 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
+    - name: Free disk space
+      run: curl -fsSL https://raw.githubusercontent.com/kou/arrow/e49d8ae15583ceff03237571569099a6ad62be32/ci/scripts/util_free_space.sh | bash
     - name: Checkout repository
       uses: actions/checkout@v5
     - name: Setup Java JDK
diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml
index 135a6d56a2..3f551f0e05 100644
--- a/.mvn/extensions.xml
+++ b/.mvn/extensions.xml
@@ -21,6 +21,6 @@ under the License.
   <extension>
     <groupId>org.apache.maven.extensions</groupId>
     <artifactId>maven-build-cache-extension</artifactId>
-    <version>1.2.0</version>
+    <version>1.2.1</version>
   </extension>
 </extensions>
diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/AMSessionDirectoryPanel.java b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/AMSessionDirectoryPanel.java
index 852f53bf9d..88d118df6a 100644
--- a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/AMSessionDirectoryPanel.java
+++ b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/AMSessionDirectoryPanel.java
@@ -123,7 +123,7 @@ public class AMSessionDirectoryPanel
             public void onClick(final AjaxRequestTarget target, final AMSession ignore) {
                 viewModal.header(new ResourceModel("details"));
                 target.add(viewModal.setContent(
-                    new JsonEditorPanel(viewModal, Model.of(model.getObject().getJson()), true, pageRef)));
+                        new JsonEditorPanel(viewModal, Model.of(model.getObject().getJson()), true, pageRef)));
                 viewModal.show(true);
             }
         }, ActionLink.ActionType.VIEW, listEntitlement);
@@ -167,8 +167,7 @@ public class AMSessionDirectoryPanel
 
     @Override
     public void onEvent(final IEvent<?> event) {
-        if (event.getPayload() instanceof AMSessionSearchEvent) {
-            AMSessionSearchEvent payload = AMSessionSearchEvent.class.cast(event.getPayload());
+        if (event.getPayload() instanceof AMSessionSearchEvent payload) {
             keyword = payload.getKeyword();
 
             updateResultTable(payload.getTarget());
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/LinkedAccountModalPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/LinkedAccountModalPanel.java
index 9cd543ca02..84b2990283 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/LinkedAccountModalPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/LinkedAccountModalPanel.java
@@ -114,8 +114,8 @@ public class LinkedAccountModalPanel extends Panel implements ModalPanel {
                 roleRestClient,
                 anyTypeRestClient.listAnyTypes().stream().map(AnyTypeTO::getKey).collect(Collectors.toList()));
         LinkedAccountFormLayoutInfo linkedAccountFormLayoutInfo =
-                anyLayout.getUser() instanceof IdMUserFormLayoutInfo
-                ? IdMUserFormLayoutInfo.class.cast(anyLayout.getUser()).getLinkedAccountFormLayoutInfo()
+                anyLayout.getUser() instanceof IdMUserFormLayoutInfo idMUFLI
+                ? idMUFLI.getLinkedAccountFormLayoutInfo()
                 : new LinkedAccountFormLayoutInfo();
 
         try {
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/RemediationDirectoryPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/RemediationDirectoryPanel.java
index 3d937cb5dd..f459a8aaa4 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/RemediationDirectoryPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/RemediationDirectoryPanel.java
@@ -345,8 +345,7 @@ public class RemediationDirectoryPanel
         @Override
         public Iterator<RemediationTO> iterator(final long first, final long count) {
             int page = ((int) first / paginatorRows);
-            return restClient.getRemediations((page < 0 ? 0 : page) + 1,
-                    paginatorRows, getSort()).iterator();
+            return restClient.getRemediations((page < 0 ? 0 : page) + 1, paginatorRows, getSort()).iterator();
         }
 
         @Override
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/policies/ProvisioningPolicyModalPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/policies/ProvisioningPolicyModalPanel.java
index c4f3fe0fac..9b4915eebe 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/policies/ProvisioningPolicyModalPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/policies/ProvisioningPolicyModalPanel.java
@@ -216,20 +216,23 @@ public class ProvisioningPolicyModalPanel extends AbstractModalPanel<Provisionin
                 @Override
                 public Boolean getObject() {
                     AbstractCorrelationRuleConf conf = correlationRule.getObject().getDefaultRuleConf();
-                    return conf instanceof DefaultInboundCorrelationRuleConf
-                            ? DefaultInboundCorrelationRuleConf.class.cast(conf).isOrSchemas()
-                            : conf instanceof DefaultPushCorrelationRuleConf
-                                    ? DefaultPushCorrelationRuleConf.class.cast(conf).isOrSchemas()
+                    return conf instanceof DefaultInboundCorrelationRuleConf dicrc
+                            ? dicrc.isOrSchemas()
+                            : conf instanceof DefaultPushCorrelationRuleConf dpcrc
+                                    ? dpcrc.isOrSchemas()
                                     : false;
                 }
 
                 @Override
                 public void setObject(final Boolean object) {
                     AbstractCorrelationRuleConf conf = correlationRule.getObject().getDefaultRuleConf();
-                    if (conf instanceof DefaultInboundCorrelationRuleConf) {
-                        DefaultInboundCorrelationRuleConf.class.cast(conf).setOrSchemas(object);
-                    } else if (conf instanceof DefaultPushCorrelationRuleConf) {
-                        DefaultPushCorrelationRuleConf.class.cast(conf).setOrSchemas(object);
+                    switch (conf) {
+                        case DefaultInboundCorrelationRuleConf dicrc ->
+                            dicrc.setOrSchemas(object);
+                        case DefaultPushCorrelationRuleConf dpcrc ->
+                            dpcrc.setOrSchemas(object);
+                        default -> {
+                        }
                     }
                 }
             };
@@ -244,10 +247,10 @@ public class ProvisioningPolicyModalPanel extends AbstractModalPanel<Provisionin
 
                 private List<String> schemas() {
                     AbstractCorrelationRuleConf conf = correlationRule.getObject().getDefaultRuleConf();
-                    return conf instanceof DefaultInboundCorrelationRuleConf
-                            ? DefaultInboundCorrelationRuleConf.class.cast(conf).getSchemas()
-                            : conf instanceof DefaultPushCorrelationRuleConf
-                                    ? DefaultPushCorrelationRuleConf.class.cast(conf).getSchemas()
+                    return conf instanceof DefaultInboundCorrelationRuleConf dicrc
+                            ? dicrc.getSchemas()
+                            : conf instanceof DefaultPushCorrelationRuleConf dpcrc
+                                    ? dpcrc.getSchemas()
                                     : List.of();
                 }
 
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/status/AnyStatusDirectoryPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/status/AnyStatusDirectoryPanel.java
index 18b42ed945..563c90358c 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/status/AnyStatusDirectoryPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/status/AnyStatusDirectoryPanel.java
@@ -257,9 +257,7 @@ public class AnyStatusDirectoryPanel
             }, ActionLink.ActionType.RECONCILIATION_PULL, IdRepoEntitlement.TASK_EXECUTE);
         }
 
-        if (anyTO instanceof UserTO && !UserTO.class.cast(anyTO).getLinkedAccounts().isEmpty()) {
-            UserTO userTO = UserTO.class.cast(anyTO);
-
+        if (anyTO instanceof UserTO userTO && userTO.getLinkedAccounts().isEmpty()) {
             if (!userTO.getLinkedAccounts().isEmpty()
                     && userTO.getLinkedAccounts().stream().anyMatch(linkedAccountTO -> {
                         return linkedAccountTO.getResource().equals(model.getObject().getResource());
@@ -272,7 +270,7 @@ public class AnyStatusDirectoryPanel
                     @Override
                     public void onClick(final AjaxRequestTarget target, final StatusBean bean) {
                         multiLevelPanelRef.next("ACCOUNTS",
-                                new LinkedAccountsStatusModalPanel(Model.of(UserTO.class.cast(anyTO)), pageRef),
+                                new LinkedAccountsStatusModalPanel(Model.of(userTO), pageRef),
                                 target);
                         target.add(multiLevelPanelRef);
                         AnyStatusDirectoryPanel.this.getTogglePanel().close(target);
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java
index cbc218e1de..a7cbcbf759 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java
@@ -240,7 +240,7 @@ public class Topology extends BasePage {
                 topologynode.setX(x);
                 topologynode.setY(y);
 
-                servers.put(String.class.cast(topologynode.getKey()), topologynode);
+                servers.put(topologynode.getKey(), topologynode);
 
                 item.add(topologyNodePanel("cs", topologynode, false));
 
@@ -279,7 +279,7 @@ public class Topology extends BasePage {
                 topologynode.setX(x);
                 topologynode.setY(y);
 
-                servers.put(String.class.cast(topologynode.getKey()), topologynode);
+                servers.put(topologynode.getKey(), topologynode);
 
                 item.add(topologyNodePanel("fp", topologynode, false));
 
@@ -336,7 +336,7 @@ public class Topology extends BasePage {
                         topologynode.setX(x);
                         topologynode.setY(y);
 
-                        connectors.put(String.class.cast(topologynode.getKey()), topologynode);
+                        connectors.put(topologynode.getKey(), topologynode);
                         item.add(topologyNodePanel("conn", topologynode, conn.isErrored()));
 
                         // Update connections
@@ -560,29 +560,26 @@ public class Topology extends BasePage {
     }
 
     @Override
-    @SuppressWarnings("unchecked")
     public void onEvent(final IEvent<?> event) {
         super.onEvent(event);
 
-        if (event.getPayload() instanceof CreateEvent) {
-            CreateEvent resourceCreateEvent = CreateEvent.class.cast(event.getPayload());
-
+        if (event.getPayload() instanceof CreateEvent payload) {
             TopologyNode node = new TopologyNode(
-                    resourceCreateEvent.getKey(),
-                    resourceCreateEvent.getDisplayName(),
-                    resourceCreateEvent.getKind());
+                    payload.getKey(),
+                    payload.getDisplayName(),
+                    payload.getKind());
 
             newlyCreated.getModelObject().add(node);
-            resourceCreateEvent.getTarget().add(newlyCreatedContainer);
+            payload.getTarget().add(newlyCreatedContainer);
 
-            resourceCreateEvent.getTarget().appendJavaScript(String.format(
+            payload.getTarget().appendJavaScript(String.format(
                     "window.Wicket.WebSocket.send('"
                     + "{\"kind\":\"%s\",\"target\":\"%s\",\"source\":\"%s\",\"scope\":\"%s\"}"
                     + "');",
                     SupportedOperation.ADD_ENDPOINT,
-                    resourceCreateEvent.getKey(),
-                    resourceCreateEvent.getParent(),
-                    resourceCreateEvent.getKind()));
+                    payload.getKey(),
+                    payload.getParent(),
+                    payload.getKind()));
         }
     }
 }
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TopologyTogglePanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TopologyTogglePanel.java
index 22d07abea0..1a775d6994 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TopologyTogglePanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TopologyTogglePanel.java
@@ -239,7 +239,7 @@ public class TopologyTogglePanel extends TogglePanel<Serializable> {
             @Override
             public void onClick(final AjaxRequestTarget target) {
                 try {
-                    connectorRestClient.delete(String.class.cast(node.getKey()));
+                    connectorRestClient.delete(node.getKey());
                     target.appendJavaScript(String.format("jsPlumb.remove('%s');", node.getKey()));
                     SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
                     toggle(target, false);
@@ -260,7 +260,7 @@ public class TopologyTogglePanel extends TogglePanel<Serializable> {
             @Override
             public void onClick(final AjaxRequestTarget target) {
                 final ResourceTO modelObject = new ResourceTO();
-                modelObject.setConnector(String.class.cast(node.getKey()));
+                modelObject.setConnector(node.getKey());
                 modelObject.setConnectorDisplayName(node.getDisplayName());
 
                 final IModel<ResourceTO> model = new CompoundPropertyModel<>(modelObject);
@@ -289,7 +289,7 @@ public class TopologyTogglePanel extends TogglePanel<Serializable> {
 
             @Override
             public void onClick(final AjaxRequestTarget target) {
-                ConnInstanceTO connInstance = connectorRestClient.read(String.class.cast(node.getKey()));
+                ConnInstanceTO connInstance = connectorRestClient.read(node.getKey());
 
                 final IModel<ConnInstanceTO> model = new CompoundPropertyModel<>(connInstance);
                 modal.setFormModel(model);
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/widgets/RemediationsWidget.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/widgets/RemediationsWidget.java
index 3fac6f481b..5bd8c17416 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/widgets/RemediationsWidget.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/widgets/RemediationsWidget.java
@@ -20,102 +20,37 @@ package org.apache.syncope.client.console.widgets;
 
 import de.agilecoders.wicket.core.markup.html.bootstrap.image.Icon;
 import de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesome5IconType;
-import java.time.Duration;
-import java.time.temporal.ChronoUnit;
-import java.util.ArrayList;
-import java.util.List;
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.pages.Remediations;
 import org.apache.syncope.client.console.rest.RemediationRestClient;
-import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior;
 import org.apache.syncope.client.ui.commons.annotations.ExtWidget;
-import org.apache.syncope.common.lib.to.RemediationTO;
 import org.apache.syncope.common.lib.types.IdMEntitlement;
 import org.apache.wicket.PageReference;
-import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
-import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
 import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.markup.html.link.AbstractLink;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.util.ListModel;
 import org.apache.wicket.spring.injection.annot.SpringBean;
 
 @ExtWidget(priority = 0)
-public class RemediationsWidget extends ExtAlertWidget<RemediationTO> {
+public class RemediationsWidget extends ExtAlertWidget {
 
     private static final long serialVersionUID = 1817429725840355068L;
 
     @SpringBean
     protected RemediationRestClient remediationRestClient;
 
-    protected final List<RemediationTO> lastRemediations = new ArrayList<>();
+    protected final boolean authorized;
 
     public RemediationsWidget(final String id, final PageReference pageRef) {
         super(id, pageRef);
-        setOutputMarkupId(true);
-
-        latestAlertsList.add(new IndicatorAjaxTimerBehavior(Duration.of(30, ChronoUnit.SECONDS)) {
-
-            private static final long serialVersionUID = 7298597675929755960L;
-
-            @Override
-            protected void onTimer(final AjaxRequestTarget target) {
-                if (!latestAlerts.getObject().equals(lastRemediations)) {
-                    refreshLatestAlerts(target);
-                }
-            }
-        });
-    }
-
-    public final void refreshLatestAlerts(final AjaxRequestTarget target) {
-        latestAlerts.getObject().clear();
-        latestAlerts.getObject().addAll(lastRemediations);
-
-        long latestAlertSize = getLatestAlertsSize();
-        linkAlertsNumber.setDefaultModelObject(latestAlertSize);
-        target.add(linkAlertsNumber);
-
-        headerAlertsNumber.setDefaultModelObject(latestAlertSize);
-        target.add(headerAlertsNumber);
-
-        target.add(latestAlertsList);
-
-        lastRemediations.clear();
-        lastRemediations.addAll(latestAlerts.getObject());
+        authorized = SyncopeConsoleSession.get().owns(IdMEntitlement.REMEDIATION_LIST);
     }
 
     @Override
     protected long getLatestAlertsSize() {
-        return SyncopeConsoleSession.get().owns(IdMEntitlement.REMEDIATION_LIST)
-                && SyncopeConsoleSession.get().owns(IdMEntitlement.REMEDIATION_READ)
-                ? remediationRestClient.countRemediations()
-                : 0L;
-    }
-
-    @Override
-    protected IModel<List<RemediationTO>> getLatestAlerts() {
-        return new ListModel<>() {
-
-            private static final long serialVersionUID = 541491929575585613L;
-
-            @Override
-            public List<RemediationTO> getObject() {
-                List<RemediationTO> updatedRemediations;
-                if (SyncopeConsoleSession.get().owns(IdMEntitlement.REMEDIATION_LIST)
-                        && SyncopeConsoleSession.get().owns(IdMEntitlement.REMEDIATION_READ)) {
-
-                    updatedRemediations = remediationRestClient.getRemediations(
-                            1, MAX_SIZE, new SortParam<>("instant", true));
-                } else {
-                    updatedRemediations = List.of();
-                }
-
-                return updatedRemediations;
-            }
-        };
+        return authorized ? remediationRestClient.countRemediations() : 0L;
     }
 
     @Override
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountWizardBuilder.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountWizardBuilder.java
index 22fcd75b05..ebf2c51c1f 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountWizardBuilder.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountWizardBuilder.java
@@ -118,9 +118,8 @@ public class LinkedAccountWizardBuilder extends BaseAjaxWizardBuilder<LinkedAcco
 
     @Override
     protected Serializable getCreateCustomPayloadEvent(final Serializable afterObject, final AjaxRequestTarget target) {
-        LinkedAccountTO linkedAccountTO = LinkedAccountTO.class.cast(afterObject);
         return new CreateEvent(
-                linkedAccountTO.getConnObjectKeyValue(),
+                LinkedAccountTO.class.cast(afterObject).getConnObjectKeyValue(),
                 model.getObject(),
                 target);
     }
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel.java
index 96cc365bec..c788b115fc 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/MergeLinkedAccountsSearchPanel.java
@@ -33,7 +33,6 @@ import org.apache.syncope.common.lib.to.AnyTypeTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.wicket.Component;
 import org.apache.wicket.PageReference;
-import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.event.IEvent;
 import org.apache.wicket.extensions.wizard.WizardStep;
 import org.apache.wicket.markup.html.WebMarkupContainer;
@@ -97,16 +96,12 @@ public class MergeLinkedAccountsSearchPanel extends WizardStep {
 
     @Override
     public void onEvent(final IEvent<?> event) {
-        if (event.getPayload() instanceof SearchClausePanel.SearchEvent) {
-            AjaxRequestTarget target = SearchClausePanel.SearchEvent.class.cast(event.getPayload()).getTarget();
+        if (event.getPayload() instanceof SearchClausePanel.SearchEvent payload) {
             String fiql = "username!~" + wizardModel.getBaseUser().getUsername() + ';'
                     + SearchUtils.buildFIQL(userSearchPanel.getModel().getObject(),
                             SyncopeClient.getUserSearchConditionBuilder());
-            userDirectoryPanel.search(fiql, target);
-        } else if (event.getPayload() instanceof final AnySelectionDirectoryPanel.ItemSelection itemSelection) {
-            AnySelectionDirectoryPanel.ItemSelection<?> payload =
-                itemSelection;
-
+            userDirectoryPanel.search(fiql, payload.getTarget());
+        } else if (event.getPayload() instanceof final AnySelectionDirectoryPanel.ItemSelection payload) {
             AnyTO sel = payload.getSelection();
             wizardModel.setMergingUser(userRestClient.read(sel.getKey()));
 
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/BaseLogin.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/BaseLogin.java
index e28ef9c2a1..74aab1280d 100644
--- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/BaseLogin.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/BaseLogin.java
@@ -37,7 +37,6 @@ import org.apache.wicket.ajax.markup.html.form.AjaxButton;
 import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.head.OnLoadHeaderItem;
 import org.apache.wicket.markup.html.WebPage;
-import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.form.ChoiceRenderer;
 import org.apache.wicket.markup.html.form.DropDownChoice;
 import org.apache.wicket.markup.html.form.PasswordTextField;
@@ -108,15 +107,6 @@ public abstract class BaseLogin extends WebPage {
                     : parameters.get(Constants.NOTIFICATION_LEVEL_PARAM).toString();
         }
 
-        Label exceptionMessage = new Label("exceptionMessage");
-        exceptionMessage.setOutputMarkupPlaceholderTag(true);
-        exceptionMessage.setVisible(false);
-        if (!parameters.get("errorMessage").isNull()) {
-            exceptionMessage.setVisible(true);
-            exceptionMessage.setDefaultModel(Model.of(parameters.get("errorMessage")));
-        }
-        add(exceptionMessage);
-
         form = new StatelessForm<>("login");
 
         usernameField = new TextField<>("username", new Model<>());
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/SyncopeUIRequestCycleListener.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/SyncopeUIRequestCycleListener.java
index 8cdbb710c5..f330576aa9 100644
--- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/SyncopeUIRequestCycleListener.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/SyncopeUIRequestCycleListener.java
@@ -32,12 +32,12 @@ import org.apache.wicket.core.request.handler.RenderPageRequestHandler;
 import org.apache.wicket.markup.html.pages.ExceptionErrorPage;
 import org.apache.wicket.protocol.http.PageExpiredException;
 import org.apache.wicket.request.IRequestHandler;
-import org.apache.wicket.request.component.IRequestablePage;
 import org.apache.wicket.request.cycle.IRequestCycleListener;
 import org.apache.wicket.request.cycle.RequestCycle;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.wicketstuff.kendo.ui.widget.notification.Notification;
 
 public abstract class SyncopeUIRequestCycleListener implements IRequestCycleListener {
 
@@ -66,61 +66,53 @@ public abstract class SyncopeUIRequestCycleListener implements IRequestCycleList
     public IRequestHandler onException(final RequestCycle cycle, final Exception e) {
         LOG.error("Exception found", e);
 
-        PageParameters errorParameters = new PageParameters();
+        PageParameters parameters = new PageParameters();
+        parameters.add(Constants.NOTIFICATION_LEVEL_PARAM, Notification.ERROR);
 
-        IRequestablePage errorPage;
         if (instanceOf(e, UnauthorizedInstantiationException.class).isPresent()) {
-            errorParameters.add("errorMessage", BaseSession.Error.AUTHORIZATION.message());
-            errorPage = getErrorPage(errorParameters);
+            parameters.add(Constants.NOTIFICATION_MSG_PARAM, BaseSession.Error.AUTHORIZATION.message());
         } else if (instanceOf(e, NotAuthorizedException.class).isPresent()) {
             NotAuthorizedException nae = instanceOf(e, NotAuthorizedException.class).get();
             if (Strings.CI.contains(nae.getMessage(), "expired")) {
-                errorParameters.add("errorMessage", BaseSession.Error.SESSION_EXPIRED.message());
+                parameters.add(Constants.NOTIFICATION_MSG_PARAM, BaseSession.Error.SESSION_EXPIRED.message());
             } else {
-                errorParameters.add("errorMessage", BaseSession.Error.AUTHORIZATION.message());
+                parameters.add(Constants.NOTIFICATION_MSG_PARAM, BaseSession.Error.AUTHORIZATION.message());
             }
-            errorPage = getErrorPage(errorParameters);
         } else if (instanceOf(e, SyncopeClientException.class).isPresent()) {
             SyncopeClientException sce = instanceOf(e, SyncopeClientException.class).get();
             String errorMessage = sce.getType() == ClientExceptionType.Unknown
                     ? String.join("", sce.getElements())
                     : sce.getMessage();
-            errorParameters.add("errorMessage", errorMessage);
-            errorPage = getErrorPage(errorParameters);
+            parameters.add(Constants.NOTIFICATION_MSG_PARAM, errorMessage);
         } else if (instanceOf(e, BadRequestException.class).isPresent()
                 || instanceOf(e, WebServiceException.class).isPresent()) {
 
-            errorParameters.add("errorMessage", BaseSession.Error.REST.message());
-            errorPage = getErrorPage(errorParameters);
+            parameters.add(Constants.NOTIFICATION_MSG_PARAM, BaseSession.Error.REST.message());
         } else if (instanceOf(e, PageExpiredException.class).isPresent() || !isSignedIn()) {
-            errorParameters.add("errorMessage", BaseSession.Error.SESSION_EXPIRED.message());
-            errorPage = getErrorPage(errorParameters);
+            parameters.add(Constants.NOTIFICATION_MSG_PARAM, BaseSession.Error.SESSION_EXPIRED.message());
         } else {
             Optional<ForbiddenException> cause = instanceOf(e, ForbiddenException.class);
             if (cause.isPresent()) {
-                errorParameters.add("errorMessage", cause.get().getMessage());
-                errorPage = getErrorPage(errorParameters);
+                parameters.add(Constants.NOTIFICATION_MSG_PARAM, cause.get().getMessage());
             } else {
                 // redirect to default Wicket error page
-                errorPage = new ExceptionErrorPage(e, null);
+                return new RenderPageRequestHandler(new PageProvider(new ExceptionErrorPage(e, null)));
             }
         }
 
-        if (errorPage instanceof BaseLogin) {
-            try {
-                invalidateSession();
-            } catch (Throwable t) {
-                // ignore
-                LOG.debug("Unexpected error while forcing logout after error", t);
-            }
+        try {
+            invalidateSession();
+        } catch (Throwable t) {
+            // ignore
+            LOG.debug("Unexpected error while forcing logout after error", t);
         }
 
-        return new RenderPageRequestHandler(new PageProvider(errorPage));
+        return new RenderPageRequestHandler(getErrorPageClass(), parameters);
     }
 
     protected abstract boolean isSignedIn();
 
     protected abstract void invalidateSession();
 
-    protected abstract IRequestablePage getErrorPage(PageParameters errorParameters);
+    protected abstract Class<? extends BaseLogin> getErrorPageClass();
 }
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/AbstractGroups.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/AbstractGroups.java
index 17ea3f1bd5..45c9fdf27d 100644
--- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/AbstractGroups.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/AbstractGroups.java
@@ -55,11 +55,12 @@ public abstract class AbstractGroups extends WizardStep implements ICondition {
         // ------------------
         // insert changed label if needed
         // ------------------
-        if (modelObject instanceof UserWrapper
-                && UserWrapper.class.cast(modelObject).getPreviousUserTO() != null
+        if (modelObject instanceof final UserWrapper uw
+                && uw.getPreviousUserTO() != null
                 && !ListUtils.isEqualList(
-                        UserWrapper.class.cast(modelObject).getInnerObject().getMemberships(),
-                        UserWrapper.class.cast(modelObject).getPreviousUserTO().getMemberships())) {
+                        uw.getInnerObject().getMemberships(),
+                        uw.getPreviousUserTO().getMemberships())) {
+
             groupsContainer.add(new LabelInfo("changed", StringUtils.EMPTY));
         } else {
             groupsContainer.add(new Label("changed", StringUtils.EMPTY));
diff --git a/client/idrepo/console/pom.xml b/client/idrepo/console/pom.xml
index 40322c3bca..380927752f 100644
--- a/client/idrepo/console/pom.xml
+++ b/client/idrepo/console/pom.xml
@@ -48,17 +48,29 @@ under the License.
       <groupId>com.giffing.wicket.spring.boot.starter</groupId>
       <artifactId>wicket-spring-boot-starter</artifactId>
     </dependency>
+
     <dependency>
       <groupId>org.springframework.boot</groupId>
-      <artifactId>spring-boot-starter-log4j2</artifactId>
+      <artifactId>spring-boot-starter-actuator</artifactId>
     </dependency>
+
     <dependency>
       <groupId>org.springframework.boot</groupId>
-      <artifactId>spring-boot-starter-actuator</artifactId>
+      <artifactId>spring-boot-starter-security</artifactId>
     </dependency>
+
     <dependency>
       <groupId>org.springframework.boot</groupId>
-      <artifactId>spring-boot-starter-security</artifactId>
+      <artifactId>spring-boot-starter-cache</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.github.ben-manes.caffeine</groupId>
+      <artifactId>jcache</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-log4j2</artifactId>
     </dependency>
 
     <dependency>
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/IdRepoConsoleContext.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/IdRepoConsoleContext.java
index 7750d736b9..0f85093b9e 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/IdRepoConsoleContext.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/IdRepoConsoleContext.java
@@ -18,7 +18,13 @@
  */
 package org.apache.syncope.client.console;
 
+import java.time.OffsetDateTime;
 import java.util.List;
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.expiry.CreatedExpiryPolicy;
+import javax.cache.expiry.Duration;
 import org.apache.syncope.client.console.commons.AccessPolicyConfProvider;
 import org.apache.syncope.client.console.commons.AnyDirectoryPanelAdditionalActionLinksProvider;
 import org.apache.syncope.client.console.commons.AnyDirectoryPanelAdditionalActionsProvider;
@@ -66,6 +72,7 @@ import org.apache.syncope.client.console.rest.UserSelfRestClient;
 import org.apache.syncope.client.ui.commons.MIMETypesLoader;
 import org.apache.syncope.client.ui.commons.PreviewUtils;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
@@ -154,6 +161,36 @@ public class IdRepoConsoleContext {
         return new IdRepoAccessPolicyConfProvider();
     }
 
+    @ConditionalOnMissingBean
+    @Bean
+    public ServletListenerRegistrationBean<SessionTimeoutListener> sessionTimeoutListener() {
+        ServletListenerRegistrationBean<SessionTimeoutListener> bean = new ServletListenerRegistrationBean<>();
+        bean.setListener(new SessionTimeoutListener());
+        return bean;
+    }
+
+    @ConditionalOnMissingBean(name = SyncopeWebApplication.LOGGEDOUT_SESSIONID_CACHE)
+    @Bean(name = SyncopeWebApplication.LOGGEDOUT_SESSIONID_CACHE)
+    public Cache<String, OffsetDateTime> loggedoutSessionIdCache(final CacheManager cacheManager) {
+        return cacheManager.createCache(SyncopeWebApplication.LOGGEDOUT_SESSIONID_CACHE,
+                new MutableConfiguration<String, OffsetDateTime>().
+                        setTypes(String.class, OffsetDateTime.class).
+                        setStoreByValue(false).
+                        setReadThrough(true).
+                        setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(Duration.TEN_MINUTES)));
+    }
+
+    @ConditionalOnMissingBean(name = SyncopeWebApplication.DESTROYED_SESSIONID_CACHE)
+    @Bean(name = SyncopeWebApplication.DESTROYED_SESSIONID_CACHE)
+    public Cache<String, OffsetDateTime> destroyedSessionIdCache(final CacheManager cacheManager) {
+        return cacheManager.createCache(SyncopeWebApplication.DESTROYED_SESSIONID_CACHE,
+                new MutableConfiguration<String, OffsetDateTime>().
+                        setTypes(String.class, OffsetDateTime.class).
+                        setStoreByValue(false).
+                        setReadThrough(true).
+                        setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(Duration.TEN_MINUTES)));
+    }
+
     @ConditionalOnMissingBean
     @Bean
     public AccessTokenRestClient accessTokenRestClient() {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/ajax/IndicatorAjaxTimerBehavior.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SessionTimeoutListener.java
similarity index 55%
rename from client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/ajax/IndicatorAjaxTimerBehavior.java
rename to client/idrepo/console/src/main/java/org/apache/syncope/client/console/SessionTimeoutListener.java
index db6899674d..44f967196c 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/ajax/IndicatorAjaxTimerBehavior.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SessionTimeoutListener.java
@@ -16,26 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.console.wicket.ajax;
+package org.apache.syncope.client.console;
 
-import java.time.Duration;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.wicket.ajax.AbstractAjaxTimerBehavior;
-import org.apache.wicket.ajax.IAjaxIndicatorAware;
+import com.giffing.wicket.spring.boot.starter.web.WicketWebInitializer;
+import jakarta.servlet.http.HttpSessionEvent;
+import jakarta.servlet.http.HttpSessionListener;
+import org.apache.wicket.Application;
 
-/**
- * An {@link AbstractAjaxTimerBehavior} not showing veil.
- */
-public abstract class IndicatorAjaxTimerBehavior extends AbstractAjaxTimerBehavior implements IAjaxIndicatorAware {
-
-    private static final long serialVersionUID = 8863750325559215077L;
-
-    public IndicatorAjaxTimerBehavior(final Duration updateInterval) {
-        super(updateInterval);
-    }
+public class SessionTimeoutListener implements HttpSessionListener {
 
     @Override
-    public String getAjaxIndicatorMarkupId() {
-        return StringUtils.EMPTY;
+    public void sessionDestroyed(final HttpSessionEvent event) {
+        ((SyncopeWebApplication) Application.get(WicketWebInitializer.WICKET_FILTERNAME)).
+                storeDestroyedSessionId(event.getSession().getId());
     }
 }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
index a79d100fc9..74bb1be384 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
@@ -19,8 +19,10 @@
 package org.apache.syncope.client.console;
 
 import com.giffing.wicket.spring.boot.starter.web.config.WicketWebInitializerAutoConfig.WebSocketWicketWebInitializerAutoConfiguration;
+import java.time.OffsetDateTime;
 import java.util.List;
 import java.util.Map;
+import javax.cache.Cache;
 import org.apache.syncope.client.console.commons.AccessPolicyConfProvider;
 import org.apache.syncope.client.console.commons.AnyDirectoryPanelAdditionalActionLinksProvider;
 import org.apache.syncope.client.console.commons.AnyDirectoryPanelAdditionalActionsProvider;
@@ -37,16 +39,19 @@ import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
 import org.apache.wicket.request.resource.IResource;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.builder.SpringApplicationBuilder;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
 
 @SpringBootApplication(proxyBeanMethods = false)
 @EnableConfigurationProperties(ConsoleProperties.class)
+@EnableCaching
 public class SyncopeConsoleApplication extends SpringBootServletInitializer {
 
     public static void main(final String[] args) {
@@ -78,7 +83,11 @@ public class SyncopeConsoleApplication extends SpringBootServletInitializer {
             final AccessPolicyConfProvider accessPolicyConfProvider,
             final List<PolicyTabProvider> policyTabProviders,
             final List<UserFormFinalizer> userFormFinalizers,
-            final List<IResource> resources) {
+            final List<IResource> resources,
+            @Qualifier(SyncopeWebApplication.LOGGEDOUT_SESSIONID_CACHE)
+            final Cache<String, OffsetDateTime> loggedoutSessionIdCache,
+            @Qualifier(SyncopeWebApplication.DESTROYED_SESSIONID_CACHE)
+            final Cache<String, OffsetDateTime> destroyedSessionIdCache) {
 
         return new SyncopeWebApplication(
                 props,
@@ -92,7 +101,9 @@ public class SyncopeConsoleApplication extends SpringBootServletInitializer {
                 accessPolicyConfProvider,
                 policyTabProviders,
                 userFormFinalizers,
-                resources);
+                resources,
+                loggedoutSessionIdCache,
+                destroyedSessionIdCache);
     }
 
     @ConditionalOnMissingBean
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
index d55c451f3c..53586cd811 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
@@ -65,6 +65,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.core.task.SimpleAsyncTaskExecutor;
 import org.springframework.core.task.TaskRejectedException;
+import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler;
 import org.springframework.util.CollectionUtils;
 
 public class SyncopeConsoleSession extends AuthenticatedWebSession implements BaseSession {
@@ -133,7 +134,7 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession implements Ba
 
         clientFactory = SyncopeWebApplication.get().newClientFactory();
 
-        executor = new SimpleAsyncTaskExecutor();
+        executor = new SimpleAsyncTaskScheduler();
         executor.setVirtualThreads(true);
     }
 
@@ -270,12 +271,15 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession implements Ba
 
     @Override
     public void invalidate() {
+        SyncopeWebApplication.get().storeLoggedOutSessionId(getId());
+
         if (getJWT() != null) {
             if (client != null) {
                 client.logout();
             }
             cleanup();
         }
+
         super.invalidate();
     }
 
@@ -324,16 +328,15 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession implements Ba
                         map(RealmsUtils::getFullPath).collect(Collectors.toSet());
                 if (requested.isEmpty()) {
                     return !owned.isEmpty();
-                } else {
-                    for (String realm : requested) {
-                        if (realm.startsWith(SyncopeConstants.ROOT_REALM)) {
-                            owns |= owned.stream().anyMatch(realm::startsWith);
-                        } else {
-                            owns |= owned.contains(realm);
-                        }
-                    }
                 }
 
+                for (String realm : requested) {
+                    if (realm.startsWith(SyncopeConstants.ROOT_REALM)) {
+                        owns |= owned.stream().anyMatch(realm::startsWith);
+                    } else {
+                        owns |= owned.contains(realm);
+                    }
+                }
                 return owns;
             }
         }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java
index ade79641a5..ab755d4a9f 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java
@@ -23,9 +23,12 @@ import de.agilecoders.wicket.core.Bootstrap;
 import de.agilecoders.wicket.core.settings.BootstrapSettings;
 import de.agilecoders.wicket.core.settings.IBootstrapSettings;
 import de.agilecoders.wicket.core.settings.SingleThemeProvider;
+import jakarta.servlet.http.Cookie;
+import java.time.OffsetDateTime;
 import java.util.Collection;
 import java.util.List;
-import java.util.stream.Collectors;
+import java.util.Optional;
+import javax.cache.Cache;
 import org.apache.syncope.client.console.annotations.UserFormFinalize;
 import org.apache.syncope.client.console.commons.AccessPolicyConfProvider;
 import org.apache.syncope.client.console.commons.AnyDirectoryPanelAdditionalActionLinksProvider;
@@ -45,6 +48,8 @@ import org.apache.syncope.client.console.rest.RealmRestClient;
 import org.apache.syncope.client.console.wizards.any.UserFormFinalizer;
 import org.apache.syncope.client.lib.SyncopeAnonymousClient;
 import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
+import org.apache.syncope.client.ui.commons.BaseLogin;
+import org.apache.syncope.client.ui.commons.BaseSession;
 import org.apache.syncope.client.ui.commons.BaseWebApplication;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.SyncopeUIRequestCycleListener;
@@ -55,6 +60,7 @@ import org.apache.syncope.common.keymaster.client.api.ServiceOps;
 import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.apache.syncope.common.rest.api.beans.RealmQuery;
 import org.apache.wicket.Page;
+import org.apache.wicket.RestartResponseAtInterceptPageException;
 import org.apache.wicket.authroles.authentication.AbstractAuthenticatedWebSession;
 import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
 import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
@@ -63,21 +69,28 @@ import org.apache.wicket.protocol.http.WebApplication;
 import org.apache.wicket.protocol.http.servlet.XForwardedRequestWrapperFactory;
 import org.apache.wicket.protocol.ws.WebSocketAwareResourceIsolationRequestCycleListener;
 import org.apache.wicket.protocol.ws.api.WebSocketResponse;
-import org.apache.wicket.request.component.IRequestablePage;
 import org.apache.wicket.request.cycle.IRequestCycleListener;
 import org.apache.wicket.request.cycle.RequestCycle;
 import org.apache.wicket.request.http.WebResponse;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.apache.wicket.request.resource.IResource;
 import org.apache.wicket.request.resource.ResourceReference;
+import org.apache.wicket.util.cookies.CookieUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.aop.support.AopUtils;
+import org.wicketstuff.kendo.ui.widget.notification.Notification;
 
 public class SyncopeWebApplication extends WicketBootSecuredWebApplication implements BaseWebApplication {
 
     protected static final Logger LOG = LoggerFactory.getLogger(SyncopeWebApplication.class);
 
+    public static final String LOGGEDOUT_SESSIONID_CACHE = "loggedoutSessionIdCache";
+
+    public static final String DESTROYED_SESSIONID_CACHE = "destroyedSessionIdCache";
+
+    protected static final CookieUtils COOKIE_UTILS = new CookieUtils();
+
     public static SyncopeWebApplication get() {
         return (SyncopeWebApplication) WebApplication.get();
     }
@@ -108,6 +121,10 @@ public class SyncopeWebApplication extends WicketBootSecuredWebApplication imple
 
     protected final List<IResource> resources;
 
+    protected final Cache<String, OffsetDateTime> loggedoutSessionIdCache;
+
+    protected final Cache<String, OffsetDateTime> destroyedSessionIdCache;
+
     public SyncopeWebApplication(
             final ConsoleProperties props,
             final ClassPathScanImplementationLookup lookup,
@@ -121,7 +138,9 @@ public class SyncopeWebApplication extends WicketBootSecuredWebApplication imple
             final AccessPolicyConfProvider accessPolicyConfProvider,
             final List<PolicyTabProvider> policyTabProviders,
             final List<UserFormFinalizer> userFormFinalizers,
-            final List<IResource> resources) {
+            final List<IResource> resources,
+            final Cache<String, OffsetDateTime> loggedoutSessionIdCache,
+            final Cache<String, OffsetDateTime> destroyedSessionIdCache) {
 
         this.props = props;
         this.lookup = lookup;
@@ -136,6 +155,8 @@ public class SyncopeWebApplication extends WicketBootSecuredWebApplication imple
         this.policyTabProviders = policyTabProviders;
         this.userFormFinalizers = userFormFinalizers;
         this.resources = resources;
+        this.loggedoutSessionIdCache = loggedoutSessionIdCache;
+        this.destroyedSessionIdCache = destroyedSessionIdCache;
     }
 
     protected SyncopeUIRequestCycleListener buildSyncopeUIRequestCycleListener() {
@@ -152,8 +173,8 @@ public class SyncopeWebApplication extends WicketBootSecuredWebApplication imple
             }
 
             @Override
-            protected IRequestablePage getErrorPage(final PageParameters errorParameters) {
-                return new Login(errorParameters);
+            protected Class<? extends BaseLogin> getErrorPageClass() {
+                return Login.class;
             }
         };
     }
@@ -312,6 +333,30 @@ public class SyncopeWebApplication extends WicketBootSecuredWebApplication imple
         return props.getMaxUploadFileSizeMB();
     }
 
+    public void storeLoggedOutSessionId(final String sessionId) {
+        loggedoutSessionIdCache.put(sessionId, OffsetDateTime.now());
+    }
+
+    public void storeDestroyedSessionId(final String sessionId) {
+        destroyedSessionIdCache.put(sessionId, OffsetDateTime.now());
+    }
+
+    @Override
+    public void restartResponseAtSignInPage() {
+        PageParameters parameters = new PageParameters();
+
+        Optional.ofNullable(COOKIE_UTILS.getCookie(COOKIE_UTILS.getSessionIdCookieName(this))).
+                map(Cookie::getValue).
+                filter(sessionId -> destroyedSessionIdCache.containsKey(sessionId)
+                && !loggedoutSessionIdCache.containsKey(sessionId)).
+                ifPresent(sessionId -> {
+                    parameters.add(Constants.NOTIFICATION_MSG_PARAM, BaseSession.Error.SESSION_EXPIRED.message());
+                    parameters.add(Constants.NOTIFICATION_LEVEL_PARAM, Notification.WARNING);
+                });
+
+        throw new RestartResponseAtInterceptPageException(getSignInPageClass(), parameters);
+    }
+
     public boolean fullRealmsTree(final RealmRestClient restClient) {
         if (props.getRealmsFullTreeThreshold() <= 0) {
             return false;
@@ -361,7 +406,7 @@ public class SyncopeWebApplication extends WicketBootSecuredWebApplication imple
             }
 
             return annotation.mode() == mode;
-        }).collect(Collectors.toList());
+        }).toList();
     }
 
     public AccessPolicyConfProvider getAccessPolicyConfProvider() {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/batch/BatchContent.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/batch/BatchContent.java
index b46f51eccf..d2605a3207 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/batch/BatchContent.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/batch/BatchContent.java
@@ -166,45 +166,38 @@ public class BatchContent<T extends Serializable, S> extends MultilevelPanel.Sec
                                 String etag = anyRestClient.read(key).getETagValue();
 
                                 switch (action) {
-                                    case DEPROVISION:
+                                    case DEPROVISION ->
                                         results.putAll(anyRestClient.deassociate(
                                                 ResourceDeassociationAction.DEPROVISION, etag, key, value));
-                                        break;
 
-                                    case UNASSIGN:
+                                    case UNASSIGN ->
                                         results.putAll(anyRestClient.deassociate(
                                                 ResourceDeassociationAction.UNASSIGN, etag, key, value));
-                                        break;
 
-                                    case UNLINK:
+                                    case UNLINK ->
                                         results.putAll(anyRestClient.deassociate(
                                                 ResourceDeassociationAction.UNLINK, etag, key, value));
-                                        break;
 
-                                    case ASSIGN:
+                                    case ASSIGN ->
                                         results.putAll(anyRestClient.associate(
                                                 ResourceAssociationAction.ASSIGN, etag, key, value));
-                                        break;
 
-                                    case LINK:
+                                    case LINK ->
                                         results.putAll(anyRestClient.associate(
                                                 ResourceAssociationAction.LINK, etag, key, value));
-                                        break;
 
-                                    case PROVISION:
+                                    case PROVISION ->
                                         results.putAll(anyRestClient.associate(
                                                 ResourceAssociationAction.PROVISION, etag, key, value));
-                                        break;
 
-                                    case SUSPEND:
+                                    case SUSPEND ->
                                         results.putAll(((UserRestClient) anyRestClient).suspend(etag, key, value));
-                                        break;
 
-                                    case REACTIVATE:
+                                    case REACTIVATE ->
                                         results.putAll(((UserRestClient) anyRestClient).reactivate(etag, key, value));
-                                        break;
 
-                                    default:
+                                    default -> {
+                                    }
                                 }
                             });
                         } else {
@@ -224,7 +217,7 @@ public class BatchContent<T extends Serializable, S> extends MultilevelPanel.Sec
                             Set<String> deletedAnys = new HashSet<>();
 
                             switch (action) {
-                                case MUSTCHANGEPASSWORD:
+                                case MUSTCHANGEPASSWORD ->
                                     items.forEach(item -> {
                                         UserTO user = (UserTO) item;
 
@@ -235,9 +228,8 @@ public class BatchContent<T extends Serializable, S> extends MultilevelPanel.Sec
 
                                         batchUserService.update(req);
                                     });
-                                    break;
 
-                                case SUSPEND:
+                                case SUSPEND ->
                                     items.forEach(item -> {
                                         UserTO user = (UserTO) item;
 
@@ -248,9 +240,8 @@ public class BatchContent<T extends Serializable, S> extends MultilevelPanel.Sec
 
                                         batchUserService.status(req);
                                     });
-                                    break;
 
-                                case REACTIVATE:
+                                case REACTIVATE ->
                                     items.forEach(item -> {
                                         UserTO user = (UserTO) item;
 
@@ -261,9 +252,8 @@ public class BatchContent<T extends Serializable, S> extends MultilevelPanel.Sec
 
                                         batchUserService.status(req);
                                     });
-                                    break;
 
-                                case DELETE:
+                                case DELETE ->
                                     items.forEach(item -> {
                                         if (singleItem instanceof AnyTO) {
                                             AnyTO any = (AnyTO) item;
@@ -285,18 +275,15 @@ public class BatchContent<T extends Serializable, S> extends MultilevelPanel.Sec
                                         }
                                     });
 
-                                    break;
-
-                                case DRYRUN:
+                                case DRYRUN ->
                                     items.forEach(item -> {
                                         TaskTO task = (TaskTO) item;
 
                                         batchTaskService.execute(
                                                 new ExecSpecs.Builder().dryRun(true).key(task.getKey()).build());
                                     });
-                                    break;
 
-                                case EXECUTE:
+                                case EXECUTE ->
                                     items.forEach(item -> {
                                         if (singleItem instanceof TaskTO) {
                                             TaskTO task = (TaskTO) item;
@@ -310,9 +297,9 @@ public class BatchContent<T extends Serializable, S> extends MultilevelPanel.Sec
                                                     new ExecSpecs.Builder().key(report.getKey()).build());
                                         }
                                     });
-                                    break;
 
-                                default:
+                                default -> {
+                                }
                             }
 
                             results = CastUtils.cast(Map.class.cast(
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java
index 6b26769a2e..f8c024e887 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java
@@ -266,9 +266,9 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
     }
 
     @SuppressWarnings("unchecked")
-    public List<Class<? extends ExtAlertWidget<?>>> getExtAlertWidgetClasses() {
+    public List<Class<? extends ExtAlertWidget>> getExtAlertWidgetClasses() {
         return classes.getOrDefault(ExtAlertWidget.class.getName(), List.of()).stream().
-                map(clazz -> (Class<? extends ExtAlertWidget<?>>) clazz).
+                map(clazz -> (Class<? extends ExtAlertWidget>) clazz).
                 collect(Collectors.toList());
     }
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
index 6a1b8fe56b..2f793bd21c 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
@@ -433,19 +433,19 @@ public class BasePage extends BaseWebPage {
         }
 
         // Extensions
-        List<Class<? extends ExtAlertWidget<?>>> extAlertWidgetClasses =
+        List<Class<? extends ExtAlertWidget>> extAlertWidgetClasses =
                 SyncopeWebApplication.get().getLookup().getExtAlertWidgetClasses();
-        ListView<Class<? extends ExtAlertWidget<?>>> extAlertWidgets = new ListView<>(
+        ListView<Class<? extends ExtAlertWidget>> extAlertWidgets = new ListView<>(
                 "extAlertWidgets", extAlertWidgetClasses) {
 
             private static final long serialVersionUID = -9112553137618363167L;
 
             @Override
-            protected void populateItem(final ListItem<Class<? extends ExtAlertWidget<?>>> item) {
+            protected void populateItem(final ListItem<Class<? extends ExtAlertWidget>> item) {
                 try {
-                    Constructor<? extends ExtAlertWidget<?>> constructor =
+                    Constructor<? extends ExtAlertWidget> constructor =
                             item.getModelObject().getDeclaredConstructor(String.class, PageReference.class);
-                    ExtAlertWidget<?> widget = constructor.newInstance("extAlertWidget", getPageReference());
+                    ExtAlertWidget widget = constructor.newInstance("extAlertWidget", getPageReference());
 
                     SyncopeConsoleSession.get().setAttribute(widget.getClass().getName(), widget);
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Realms.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Realms.java
index d2554b2f39..cbb251e84c 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Realms.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Realms.java
@@ -156,8 +156,7 @@ public class Realms extends BasePage {
             ChosenRealm<RealmTO> choosenRealm = ChosenRealm.class.cast(event.getPayload());
             updateRealmContent(choosenRealm.getObj(), 0);
             choosenRealm.getTarget().add(content);
-        } else if (event.getPayload() instanceof AjaxWizard.NewItemEvent) {
-            AjaxWizard.NewItemEvent<?> newItemEvent = AjaxWizard.NewItemEvent.class.cast(event.getPayload());
+        } else if (event.getPayload() instanceof AjaxWizard.NewItemEvent<?> newItemEvent) {
             WizardModalPanel<?> modalPanel = newItemEvent.getModalPanel();
 
             if (event.getPayload() instanceof AjaxWizard.NewItemActionEvent && modalPanel != null) {
@@ -202,10 +201,8 @@ public class Realms extends BasePage {
         @Override
         protected void setWindowClosedReloadCallback(final BaseModal<?> modal) {
             modal.setWindowClosedCallback(target -> {
-                if (modal.getContent() instanceof ResultPanel) {
-                    Object result = ResultPanel.class.cast(modal.getContent()).getResult();
-
-                    RealmTO newRealmTO = RealmTO.class.cast(ProvisioningResult.class.cast(result).getEntity());
+                if (modal.getContent() instanceof ResultPanel<?, ?> rp) {
+                    RealmTO newRealmTO = RealmTO.class.cast(ProvisioningResult.class.cast(rp.getResult()).getEntity());
                     // reload realmChoicePanel label too - SYNCOPE-1151
                     target.add(realmChoicePanel.reloadRealmTree(target, Model.of(newRealmTO)));
                     realmChoicePanel.setCurrentRealm(newRealmTO);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java
index e0868673ea..7abaa5f20b 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java
@@ -311,8 +311,8 @@ public class AnyPanel extends Panel implements ModalPanel {
 
     @Override
     public void onEvent(final IEvent<?> event) {
-        if (event.getPayload() instanceof SearchClausePanel.SearchEvent) {
-            AjaxRequestTarget target = SearchClausePanel.SearchEvent.class.cast(event.getPayload()).getTarget();
+        if (event.getPayload() instanceof SearchClausePanel.SearchEvent payload) {
+            AjaxRequestTarget target = payload.getTarget();
 
             send(AnyPanel.this.directoryPanel, Broadcast.BREADTH,
                     new ActionLinksTogglePanel.ActionLinkToggleCloseEventPayload(target));
@@ -322,7 +322,7 @@ public class AnyPanel extends Panel implements ModalPanel {
                     : String.format("$dynRealms=~%s;", realmTO.getKey());
 
             switch (anyTypeTO.getKind()) {
-                case USER:
+                case USER ->
                     UserDirectoryPanel.class.cast(AnyPanel.this.directoryPanel).search(
                             precond + SearchUtils.buildFIQL(
                                     AnyPanel.this.searchPanel.getModel().getObject(),
@@ -330,9 +330,8 @@ public class AnyPanel extends Panel implements ModalPanel {
                                     AnyPanel.this.searchPanel.getAvailableSchemaTypes(),
                                     SearchUtils.NO_CUSTOM_CONDITION),
                             target);
-                    break;
 
-                case GROUP:
+                case GROUP ->
                     GroupDirectoryPanel.class.cast(AnyPanel.this.directoryPanel).search(
                             precond + SearchUtils.buildFIQL(
                                     AnyPanel.this.searchPanel.getModel().getObject(),
@@ -340,9 +339,8 @@ public class AnyPanel extends Panel implements ModalPanel {
                                     AnyPanel.this.searchPanel.getAvailableSchemaTypes(),
                                     SearchUtils.NO_CUSTOM_CONDITION),
                             target);
-                    break;
 
-                case ANY_OBJECT:
+                case ANY_OBJECT ->
                     AnyObjectDirectoryPanel.class.cast(AnyPanel.this.directoryPanel).search(
                             precond + SearchUtils.buildFIQL(
                                     AnyPanel.this.searchPanel.getModel().getObject(),
@@ -350,9 +348,9 @@ public class AnyPanel extends Panel implements ModalPanel {
                                     AnyPanel.this.searchPanel.getAvailableSchemaTypes(),
                                     SearchUtils.NO_CUSTOM_CONDITION),
                             target);
-                    break;
 
-                default:
+                default -> {
+                }
             }
         } else {
             super.onEvent(event);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/CommandDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/CommandDirectoryPanel.java
index c6683bba7f..f96c484d64 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/CommandDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/CommandDirectoryPanel.java
@@ -139,9 +139,7 @@ public class CommandDirectoryPanel
 
     @Override
     public void onEvent(final IEvent<?> event) {
-        if (event.getPayload() instanceof KeywordSearchEvent) {
-            KeywordSearchEvent payload = KeywordSearchEvent.class.cast(event.getPayload());
-
+        if (event.getPayload() instanceof KeywordSearchEvent payload) {
             keyword = payload.getKeyword();
             if (StringUtils.isNotBlank(keyword)) {
                 if (!Strings.CS.startsWith(keyword, "*")) {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardOverviewPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardOverviewPanel.java
index 034b446e03..a0d167e205 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardOverviewPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardOverviewPanel.java
@@ -18,21 +18,19 @@
  */
 package org.apache.syncope.client.console.panels;
 
-import java.time.Duration;
-import java.time.temporal.ChronoUnit;
 import org.apache.commons.lang3.tuple.Triple;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
-import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior;
+import org.apache.syncope.client.console.wicket.ws.RefreshWebSocketBehavior;
 import org.apache.syncope.client.console.widgets.AnyByRealmWidget;
 import org.apache.syncope.client.console.widgets.CompletenessWidget;
 import org.apache.syncope.client.console.widgets.LoadWidget;
 import org.apache.syncope.client.console.widgets.NumberWidget;
 import org.apache.syncope.client.console.widgets.UsersByStatusWidget;
 import org.apache.syncope.common.lib.info.NumbersInfo;
-import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.protocol.ws.api.WebSocketRequestHandler;
 
 public class DashboardOverviewPanel extends Panel {
 
@@ -100,36 +98,36 @@ public class DashboardOverviewPanel extends Panel {
         load = new LoadWidget("load", SyncopeConsoleSession.get().getAnonymousClient().system());
         container.add(load);
 
-        container.add(new IndicatorAjaxTimerBehavior(Duration.of(60, ChronoUnit.SECONDS)) {
+        container.add(new RefreshWebSocketBehavior() {
 
-            private static final long serialVersionUID = -4426283634345968585L;
+            private static final long serialVersionUID = -7095269057058900157L;
 
             @Override
-            protected void onTimer(final AjaxRequestTarget target) {
+            protected void onTimer(final WebSocketRequestHandler handler) {
                 NumbersInfo numbers = SyncopeConsoleSession.get().getAnonymousClient().numbers();
 
                 if (totalUsers.refresh(numbers.getTotalUsers())) {
-                    target.add(totalUsers);
+                    handler.add(totalUsers);
                 }
                 if (totalGroups.refresh(numbers.getTotalGroups())) {
-                    target.add(totalGroups);
+                    handler.add(totalGroups);
                 }
 
                 Triple<Long, String, String> updatedBuild = buildTotalAny1OrRoles(numbers);
                 if (totalAny1OrRoles.refresh(updatedBuild.getLeft())) {
-                    target.add(totalAny1OrRoles);
+                    handler.add(totalAny1OrRoles);
                 }
                 updatedBuild = buildTotalAny2OrResources(numbers);
                 if (totalAny2OrResources.refresh(updatedBuild.getLeft())) {
-                    target.add(totalAny2OrResources);
+                    handler.add(totalAny2OrResources);
                 }
 
                 if (usersByStatus.refresh(numbers.getUsersByStatus())) {
-                    target.add(usersByStatus);
+                    handler.add(usersByStatus);
                 }
 
                 if (completeness.refresh(numbers.getConfCompleteness())) {
-                    target.add(completeness);
+                    handler.add(completeness);
                 }
 
                 if (anyByRealm.refresh(
@@ -140,13 +138,13 @@ public class DashboardOverviewPanel extends Panel {
                         numbers.getAnyType2(),
                         numbers.getAny2ByRealm())) {
 
-                    target.add(anyByRealm);
+                    handler.add(anyByRealm);
                 }
 
                 load.refresh(SyncopeConsoleSession.get().getAnonymousClient().system());
-                target.add(load);
+                handler.add(load);
             }
-        });
+        }.schedule(60));
     }
 
     private static Triple<Long, String, String> buildTotalAny1OrRoles(final NumbersInfo numbers) {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/LogsPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/LogsPanel.java
index 6726fa14c5..238512faa7 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/LogsPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/LogsPanel.java
@@ -107,7 +107,7 @@ public class LogsPanel extends Panel {
             protected Component getValueComponent(final String key, final LoggerConf loggerConf) {
                 if ("level".equalsIgnoreCase(key)) {
                     AjaxDropDownChoicePanel<LogLevel> loggerLevel = new AjaxDropDownChoicePanel<>(
-                        "field", getString("level"), Model.of(loggerConf.getLevel()), false);
+                            "field", getString("level"), Model.of(loggerConf.getLevel()), false);
                     MetaDataRoleAuthorizationStrategy.authorize(loggerLevel, ENABLE, IdRepoEntitlement.LOGGER_UPDATE);
 
                     loggerLevel.hideLabel();
@@ -152,8 +152,7 @@ public class LogsPanel extends Panel {
 
     @Override
     public void onEvent(final IEvent<?> event) {
-        if (event.getPayload() instanceof LoggerConfSearchEvent) {
-            LoggerConfSearchEvent payload = LoggerConfSearchEvent.class.cast(event.getPayload());
+        if (event.getPayload() instanceof LoggerConfSearchEvent payload) {
             AjaxRequestTarget target = payload.getTarget();
 
             String keyword = payload.getKeyword();
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java
index 53666bd93c..c09831636d 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java
@@ -187,9 +187,7 @@ public class SchemaTypePanel extends TypesDirectoryPanel<SchemaTO, SchemaProvide
 
     @Override
     public void onEvent(final IEvent<?> event) {
-        if (event.getPayload() instanceof KeywordSearchEvent) {
-            KeywordSearchEvent payload = KeywordSearchEvent.class.cast(event.getPayload());
-
+        if (event.getPayload() instanceof KeywordSearchEvent payload) {
             keyword = payload.getKeyword();
             if (StringUtils.isNotBlank(keyword)) {
                 if (!Strings.CS.startsWith(keyword, "*")) {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
index ea1f19297c..cb2fe69720 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
@@ -19,8 +19,6 @@
 package org.apache.syncope.client.console.reports;
 
 import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
-import java.time.Duration;
-import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
@@ -35,13 +33,13 @@ import org.apache.syncope.client.console.panels.DirectoryPanel;
 import org.apache.syncope.client.console.panels.MultilevelPanel;
 import org.apache.syncope.client.console.rest.ImplementationRestClient;
 import org.apache.syncope.client.console.rest.ReportRestClient;
-import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.KeyPropertyColumn;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
+import org.apache.syncope.client.console.wicket.ws.RefreshWebSocketBehavior;
 import org.apache.syncope.client.console.widgets.JobActionPanel;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.MIMETypesLoader;
@@ -68,6 +66,7 @@ import org.apache.wicket.model.CompoundPropertyModel;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.protocol.ws.api.WebSocketRequestHandler;
 import org.apache.wicket.spring.injection.annot.SpringBean;
 
 /**
@@ -99,16 +98,16 @@ public abstract class ReportDirectoryPanel
         modal.size(Modal.Size.Large);
         initResultTable();
 
-        container.add(new IndicatorAjaxTimerBehavior(Duration.of(10, ChronoUnit.SECONDS)) {
+        container.add(new RefreshWebSocketBehavior() {
 
             private static final long serialVersionUID = -4661303265651934868L;
 
             @Override
-            protected void onTimer(final AjaxRequestTarget target) {
+            protected void onTimer(final WebSocketRequestHandler handler) {
                 container.modelChanged();
-                target.add(container);
+                handler.add(container);
             }
-        });
+        }.schedule(10));
 
         startAt = new ReportStartAtTogglePanel(container, pageRef);
         addInnerObject(startAt);
@@ -180,9 +179,9 @@ public abstract class ReportDirectoryPanel
 
     @Override
     public void onEvent(final IEvent<?> event) {
-        if (event.getPayload() instanceof JobActionPanel.JobActionPayload) {
+        if (event.getPayload() instanceof JobActionPanel.JobActionPayload payload) {
             container.modelChanged();
-            JobActionPanel.JobActionPayload.class.cast(event.getPayload()).getTarget().add(container);
+            payload.getTarget().add(container);
         } else {
             super.onEvent(event);
         }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
index 2824293f61..fad5cfd2d2 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
@@ -18,25 +18,24 @@
  */
 package org.apache.syncope.client.console.tasks;
 
-import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import org.apache.syncope.client.console.panels.MultilevelPanel;
 import org.apache.syncope.client.console.rest.TaskRestClient;
-import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior;
 import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.console.wicket.ws.RefreshWebSocketBehavior;
 import org.apache.syncope.client.console.widgets.JobActionPanel;
 import org.apache.syncope.common.lib.to.InboundTaskTO;
 import org.apache.syncope.common.lib.to.ProvisioningTaskTO;
 import org.apache.syncope.common.lib.to.PushTaskTO;
 import org.apache.syncope.common.lib.types.TaskType;
 import org.apache.wicket.PageReference;
-import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.event.IEvent;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
 import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.protocol.ws.api.WebSocketRequestHandler;
 
 /**
  * Tasks page.
@@ -76,16 +75,16 @@ public abstract class ProvisioningTaskDirectoryPanel<T extends ProvisioningTaskT
         enableUtilityButton();
         super.initResultTable();
 
-        container.add(new IndicatorAjaxTimerBehavior(java.time.Duration.of(10, ChronoUnit.SECONDS)) {
+        container.add(new RefreshWebSocketBehavior() {
 
             private static final long serialVersionUID = -4661303265651934868L;
 
             @Override
-            protected void onTimer(final AjaxRequestTarget target) {
+            protected void onTimer(final WebSocketRequestHandler handler) {
                 container.modelChanged();
-                target.add(container);
+                handler.add(container);
             }
-        });
+        }.schedule(10));
     }
 
     @Override
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
index 079c390990..afcf37b27b 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
@@ -20,8 +20,6 @@ package org.apache.syncope.client.console.tasks;
 
 import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
 import java.io.Serializable;
-import java.time.Duration;
-import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
@@ -35,7 +33,6 @@ import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.panels.MultilevelPanel;
 import org.apache.syncope.client.console.rest.RealmRestClient;
 import org.apache.syncope.client.console.rest.TaskRestClient;
-import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.KeyPropertyColumn;
@@ -43,6 +40,7 @@ import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.Bas
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
+import org.apache.syncope.client.console.wicket.ws.RefreshWebSocketBehavior;
 import org.apache.syncope.client.console.widgets.JobActionPanel;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.panels.ModalPanel;
@@ -70,6 +68,7 @@ import org.apache.wicket.markup.repeater.Item;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.protocol.ws.api.WebSocketRequestHandler;
 import org.apache.wicket.spring.injection.annot.SpringBean;
 
 /**
@@ -121,16 +120,16 @@ public abstract class SchedTaskDirectoryPanel<T extends SchedTaskTO>
 
         initResultTable();
 
-        container.add(new IndicatorAjaxTimerBehavior(Duration.of(10, ChronoUnit.SECONDS)) {
+        container.add(new RefreshWebSocketBehavior() {
 
             private static final long serialVersionUID = -4661303265651934868L;
 
             @Override
-            protected void onTimer(final AjaxRequestTarget target) {
+            protected void onTimer(final WebSocketRequestHandler handler) {
                 container.modelChanged();
-                target.add(container);
+                handler.add(container);
             }
-        });
+        }.schedule(10));
 
         startAt = new TaskStartAtTogglePanel(container, pageRef);
         addInnerObject(startAt);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/TaskDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/TaskDirectoryPanel.java
index 23061e9caf..efe40b527e 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/TaskDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/TaskDirectoryPanel.java
@@ -108,10 +108,9 @@ public abstract class TaskDirectoryPanel<T extends TaskTO>
     @Override
     public void onEvent(final IEvent<?> event) {
         super.onEvent(event);
-        if (event.getPayload() instanceof ExitEvent) {
-            AjaxRequestTarget target = ExitEvent.class.cast(event.getPayload()).getTarget();
+        if (event.getPayload() instanceof ExitEvent exitEvent) {
             baseModal.show(false);
-            baseModal.close(target);
+            baseModal.close(exitEvent.getTarget());
         }
     }
 }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksTogglePanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksTogglePanel.java
index 983c6f039d..57a6e98268 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksTogglePanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksTogglePanel.java
@@ -153,8 +153,8 @@ public class ActionLinksTogglePanel<T extends Serializable> extends TogglePanel<
 
     @Override
     public void onEvent(final IEvent<?> event) {
-        if (event.getPayload() instanceof ActionLinkToggleCloseEventPayload) {
-            close(ActionLinkToggleCloseEventPayload.class.cast(event.getPayload()).getTarget());
+        if (event.getPayload() instanceof final ActionLinkToggleCloseEventPayload payload) {
+            close(payload.getTarget());
         }
     }
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/ws/RefreshWebSocketBehavior.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/ws/RefreshWebSocketBehavior.java
new file mode 100644
index 0000000000..a95073f32a
--- /dev/null
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/ws/RefreshWebSocketBehavior.java
@@ -0,0 +1,77 @@
+/*
+ * 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.syncope.client.console.wicket.ws;
+
+import com.giffing.wicket.spring.boot.starter.web.WicketWebInitializer;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.mutable.MutableObject;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.wicket.Application;
+import org.apache.wicket.protocol.ws.WebSocketSettings;
+import org.apache.wicket.protocol.ws.api.IWebSocketConnection;
+import org.apache.wicket.protocol.ws.api.WebSocketBehavior;
+import org.apache.wicket.protocol.ws.api.WebSocketRequestHandler;
+import org.apache.wicket.protocol.ws.api.message.ConnectedMessage;
+import org.apache.wicket.protocol.ws.api.message.IWebSocketPushMessage;
+import org.apache.wicket.protocol.ws.api.registry.IKey;
+
+public abstract class RefreshWebSocketBehavior extends WebSocketBehavior {
+
+    private static final long serialVersionUID = 5636572627689425575L;
+
+    protected final Mutable<Pair<String, IKey>> websocketInfo = new MutableObject<>();
+
+    @Override
+    protected void onConnect(final ConnectedMessage message) {
+        websocketInfo.setValue(Pair.of(message.getSessionId(), message.getKey()));
+    }
+
+    protected abstract void onTimer(WebSocketRequestHandler handler);
+
+    @Override
+    protected void onPush(final WebSocketRequestHandler handler, final IWebSocketPushMessage message) {
+        onTimer(handler);
+    }
+
+    public RefreshWebSocketBehavior schedule(final long periodSeconds) {
+        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, Thread.ofVirtual().factory());
+        executor.scheduleAtFixedRate(() -> {
+            if (websocketInfo.get() == null) {
+                return;
+            }
+
+            Application application = Application.get(WicketWebInitializer.WICKET_FILTERNAME);
+            IWebSocketConnection wsConnection = WebSocketSettings.Holder.get(application).getConnectionRegistry().
+                    getConnection(application, websocketInfo.get().getLeft(), websocketInfo.get().getRight());
+            if (wsConnection != null && wsConnection.isOpen()) {
+                wsConnection.sendMessage(new IWebSocketPushMessage() {
+
+                    private static final long serialVersionUID = -4425174034118785972L;
+
+                });
+            } else {
+                executor.shutdownNow();
+            }
+        }, 0, periodSeconds, TimeUnit.SECONDS);
+
+        return this;
+    }
+}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/AlertWidget.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/AlertWidget.java
index 30328df69a..93086bb200 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/AlertWidget.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/AlertWidget.java
@@ -19,41 +19,29 @@
 package org.apache.syncope.client.console.widgets;
 
 import de.agilecoders.wicket.core.markup.html.bootstrap.image.Icon;
-import java.io.Serializable;
-import java.util.List;
 import org.apache.wicket.markup.ComponentTag;
-import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.link.AbstractLink;
 import org.apache.wicket.markup.html.panel.Panel;
-import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.LoadableDetachableModel;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public abstract class AlertWidget<T extends Serializable> extends Panel {
+public abstract class AlertWidget extends Panel {
 
     private static final long serialVersionUID = 7667120094526529934L;
 
     protected static final Logger LOG = LoggerFactory.getLogger(AlertWidget.class);
 
-    protected static final int MAX_SIZE = 5;
-
     protected final Label linkAlertsNumber;
 
     protected final Label headerAlertsNumber;
 
-    protected final WebMarkupContainer latestAlertsList;
-
-    protected IModel<List<T>> latestAlerts;
-
     public AlertWidget(final String id) {
         super(id);
-        this.latestAlerts = getLatestAlerts();
-
         setOutputMarkupId(true);
 
-        final LoadableDetachableModel<Long> size = new LoadableDetachableModel<>() {
+        LoadableDetachableModel<Long> size = new LoadableDetachableModel<>() {
 
             private static final long serialVersionUID = 7474274077691068779L;
 
@@ -90,21 +78,12 @@ public abstract class AlertWidget<T extends Serializable> extends Panel {
         add(linkAlertsNumber.setOutputMarkupId(true));
 
         headerAlertsNumber = new Label("number", size);
-        headerAlertsNumber.setOutputMarkupId(true);
-        add(headerAlertsNumber);
+        add(headerAlertsNumber.setOutputMarkupId(true));
 
         add(getEventsLink("alertsLink"));
-
-        latestAlertsList = new WebMarkupContainer("latestAlertsList");
-        latestAlertsList.setOutputMarkupId(true);
-        add(latestAlertsList);
-    }
-
-    protected long getLatestAlertsSize() {
-        return latestAlerts.getObject().size();
     }
 
-    protected abstract IModel<List<T>> getLatestAlerts();
+    protected abstract long getLatestAlertsSize();
 
     protected abstract AbstractLink getEventsLink(String linkid);
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ExtAlertWidget.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ExtAlertWidget.java
index 3d35489916..56039dd993 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ExtAlertWidget.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ExtAlertWidget.java
@@ -18,10 +18,11 @@
  */
 package org.apache.syncope.client.console.widgets;
 
-import java.io.Serializable;
+import org.apache.syncope.client.console.wicket.ws.RefreshWebSocketBehavior;
 import org.apache.wicket.PageReference;
+import org.apache.wicket.protocol.ws.api.WebSocketRequestHandler;
 
-public abstract class ExtAlertWidget<T extends Serializable> extends AlertWidget<T> {
+public abstract class ExtAlertWidget extends AlertWidget {
 
     private static final long serialVersionUID = -5622060468533516192L;
 
@@ -30,5 +31,22 @@ public abstract class ExtAlertWidget<T extends Serializable> extends AlertWidget
     public ExtAlertWidget(final String id, final PageReference pageRef) {
         super(id);
         this.pageRef = pageRef;
+
+        add(new RefreshWebSocketBehavior() {
+
+            private static final long serialVersionUID = -7095269057058900157L;
+
+            @Override
+            protected void onTimer(final WebSocketRequestHandler handler) {
+                long latestAlterts = getLatestAlertsSize();
+                if (!String.valueOf(latestAlterts).equals(linkAlertsNumber.getDefaultModelObjectAsString())) {
+                    linkAlertsNumber.setDefaultModelObject(latestAlterts);
+                    handler.add(linkAlertsNumber);
+
+                    headerAlertsNumber.setDefaultModelObject(latestAlterts);
+                    handler.add(headerAlertsNumber);
+                }
+            }
+        }.schedule(30));
     }
 }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ItemTransformerWidget.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ItemTransformerWidget.java
index 3f74823941..c31cf85915 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ItemTransformerWidget.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ItemTransformerWidget.java
@@ -20,16 +20,13 @@ package org.apache.syncope.client.console.widgets;
 
 import de.agilecoders.wicket.core.markup.html.bootstrap.image.Icon;
 import de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesome5IconType;
-import java.util.List;
 import org.apache.syncope.client.console.wizards.mapping.ItemTransformersTogglePanel;
 import org.apache.syncope.common.lib.to.Item;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.markup.html.AjaxLink;
 import org.apache.wicket.markup.html.link.AbstractLink;
-import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.util.ListModel;
 
-public class ItemTransformerWidget extends AlertWidget<String> {
+public class ItemTransformerWidget extends AlertWidget {
 
     private static final long serialVersionUID = 7667120094526529934L;
 
@@ -45,20 +42,11 @@ public class ItemTransformerWidget extends AlertWidget<String> {
         super(id);
         this.item = item;
         this.transformers = transformers;
-        setOutputMarkupId(true);
     }
 
     @Override
-    protected IModel<List<String>> getLatestAlerts() {
-        return new ListModel<>() {
-
-            private static final long serialVersionUID = 1232998477036705088L;
-
-            @Override
-            public List<String> getObject() {
-                return item.getTransformers();
-            }
-        };
+    protected long getLatestAlertsSize() {
+        return item.getTransformers().size();
     }
 
     @Override
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/JEXLTransformerWidget.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/JEXLTransformerWidget.java
index 64b809e679..5bb6ad85c7 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/JEXLTransformerWidget.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/JEXLTransformerWidget.java
@@ -20,18 +20,14 @@ package org.apache.syncope.client.console.widgets;
 
 import de.agilecoders.wicket.core.markup.html.bootstrap.image.Icon;
 import de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesome5IconType;
-import java.util.ArrayList;
-import java.util.List;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.console.wizards.mapping.JEXLTransformersTogglePanel;
 import org.apache.syncope.common.lib.to.Item;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.markup.html.AjaxLink;
 import org.apache.wicket.markup.html.link.AbstractLink;
-import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.util.ListModel;
 
-public class JEXLTransformerWidget extends AlertWidget<String> {
+public class JEXLTransformerWidget extends AlertWidget {
 
     private static final long serialVersionUID = 7667120094526529934L;
 
@@ -45,32 +41,21 @@ public class JEXLTransformerWidget extends AlertWidget<String> {
             final JEXLTransformersTogglePanel transformers) {
 
         super(id);
-        setOutputMarkupId(true);
 
         this.item = item;
         this.transformers = transformers;
-
-        this.latestAlertsList.setVisible(false);
     }
 
     @Override
-    protected IModel<List<String>> getLatestAlerts() {
-        return new ListModel<>() {
-
-            private static final long serialVersionUID = -2583290457773357445L;
-
-            @Override
-            public List<String> getObject() {
-                List<String> result = new ArrayList<>();
-                if (StringUtils.isNotBlank(item.getPropagationJEXLTransformer())) {
-                    result.add(item.getPropagationJEXLTransformer());
-                }
-                if (StringUtils.isNotBlank(item.getPullJEXLTransformer())) {
-                    result.add(item.getPullJEXLTransformer());
-                }
-                return result;
-            }
-        };
+    protected long getLatestAlertsSize() {
+        long size = 0;
+        if (StringUtils.isNotBlank(item.getPropagationJEXLTransformer())) {
+            size++;
+        }
+        if (StringUtils.isNotBlank(item.getPullJEXLTransformer())) {
+            size++;
+        }
+        return size;
     }
 
     @Override
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
index f5556a5556..fd274ac0f5 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java
@@ -21,8 +21,6 @@ package org.apache.syncope.client.console.widgets;
 import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
 import de.agilecoders.wicket.core.markup.html.bootstrap.tabs.AjaxBootstrapTabbedPanel;
 import java.io.Serializable;
-import java.time.Duration;
-import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
@@ -43,7 +41,6 @@ import org.apache.syncope.client.console.rest.RealmRestClient;
 import org.apache.syncope.client.console.rest.ReportRestClient;
 import org.apache.syncope.client.console.rest.TaskRestClient;
 import org.apache.syncope.client.console.tasks.SchedTaskWizardBuilder;
-import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn;
 import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
@@ -51,6 +48,7 @@ import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksTogglePanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
+import org.apache.syncope.client.console.wicket.ws.RefreshWebSocketBehavior;
 import org.apache.syncope.client.console.wizards.WizardMgtPanel;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.MIMETypesLoader;
@@ -85,6 +83,7 @@ import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.model.ResourceModel;
 import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.protocol.ws.api.WebSocketRequestHandler;
 import org.apache.wicket.spring.injection.annot.SpringBean;
 
 public class JobWidget extends BaseWidget {
@@ -174,19 +173,19 @@ public class JobWidget extends BaseWidget {
         recent = getUpdatedRecent();
 
         container = new WebMarkupContainer("jobContainer");
-        container.add(new IndicatorAjaxTimerBehavior(Duration.of(10, ChronoUnit.SECONDS)) {
+        container.add(new RefreshWebSocketBehavior() {
 
             private static final long serialVersionUID = 7298597675929755960L;
 
             @Override
-            protected void onTimer(final AjaxRequestTarget target) {
+            protected void onTimer(final WebSocketRequestHandler handler) {
                 List<JobTO> updatedAvailable = getUpdatedAvailable();
                 if (!updatedAvailable.equals(available)) {
                     available.clear();
                     available.addAll(updatedAvailable);
                     if (availableJobsPanel != null) {
                         availableJobsPanel.modelChanged();
-                        target.add(availableJobsPanel);
+                        handler.add(availableJobsPanel);
                     }
                 }
 
@@ -196,11 +195,11 @@ public class JobWidget extends BaseWidget {
                     recent.addAll(updatedRecent);
                     if (recentExecPanel != null) {
                         recentExecPanel.modelChanged();
-                        target.add(recentExecPanel);
+                        handler.add(recentExecPanel);
                     }
                 }
             }
-        });
+        }.schedule(10));
         add(container);
 
         container.add(new AjaxBootstrapTabbedPanel<>("tabbedPanel", buildTabList(pageRef)));
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/UserSelectionWizardStep.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/UserSelectionWizardStep.java
index cc3feb98bd..c9ce1a1074 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/UserSelectionWizardStep.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/UserSelectionWizardStep.java
@@ -29,11 +29,9 @@ import org.apache.syncope.client.console.rest.AnyTypeRestClient;
 import org.apache.syncope.client.console.rest.UserRestClient;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.to.AnyTypeTO;
-import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.wicket.Component;
 import org.apache.wicket.PageReference;
-import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.event.IEvent;
 import org.apache.wicket.extensions.wizard.WizardStep;
 import org.apache.wicket.model.IModel;
@@ -82,25 +80,27 @@ public class UserSelectionWizardStep extends WizardStep {
 
     @Override
     public void onEvent(final IEvent<?> event) {
-        if (event.getPayload() instanceof SearchClausePanel.SearchEvent) {
-            AjaxRequestTarget target = SearchClausePanel.SearchEvent.class.cast(event.getPayload()).getTarget();
-            String fiql = SearchUtils.buildFIQL(
-                    userSearchPanel.getModel().getObject(), SyncopeClient.getUserSearchConditionBuilder());
-            userDirectoryPanel.search(fiql, target);
-        } else if (event.getPayload() instanceof AnySelectionDirectoryPanel.ItemSelection) {
-            @SuppressWarnings("unchecked")
-            AnySelectionDirectoryPanel.ItemSelection<UserTO> payload =
-                    (AnySelectionDirectoryPanel.ItemSelection<UserTO>) event.getPayload();
-
-            UserTO selected = payload.getSelection();
-            this.model.setObject(selected.getKey());
-
-            String tableId = ((Component) event.getSource()).
-                    get("container:content:searchContainer:resultTable:tablePanel:groupForm:checkgroup:dataTable").
-                    getMarkupId();
-            String js = "$('#" + tableId + " tr').removeClass('active');";
-            js += "$('#" + tableId + " td[title=" + selected.getKey() + "]').parent().addClass('active');";
-            payload.getTarget().prependJavaScript(js);
+        switch (event.getPayload()) {
+            case SearchClausePanel.SearchEvent payload -> {
+                String fiql = SearchUtils.buildFIQL(
+                        userSearchPanel.getModel().getObject(), SyncopeClient.getUserSearchConditionBuilder());
+                userDirectoryPanel.search(fiql, payload.getTarget());
+            }
+
+            case AnySelectionDirectoryPanel.ItemSelection<?> payload -> {
+                this.model.setObject(payload.getSelection().getKey());
+
+                String tableId = ((Component) event.getSource()).
+                        get("container:content:searchContainer:resultTable:tablePanel:groupForm:checkgroup:dataTable").
+                        getMarkupId();
+                String js = "$('#" + tableId + " tr').removeClass('active');";
+                js += "$('#" + tableId + " td[title=" + payload.getSelection().getKey()
+                        + "]').parent().addClass('active');";
+                payload.getTarget().prependJavaScript(js);
+            }
+
+            default -> {
+            }
         }
     }
 }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/WizardMgtPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/WizardMgtPanel.java
index 436ff083fe..ee8a096d39 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/WizardMgtPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/WizardMgtPanel.java
@@ -167,10 +167,9 @@ public abstract class WizardMgtPanel<T extends Serializable> extends AbstractWiz
     @Override
     @SuppressWarnings("unchecked")
     public void onEvent(final IEvent<?> event) {
-        if (event.getPayload() instanceof ExitEvent) {
-            AjaxRequestTarget target = ExitEvent.class.cast(event.getPayload()).getTarget();
+        if (event.getPayload() instanceof final ExitEvent payload) {
             // default behaviour: change it catching the event if needed
-            modal.close(target);
+            modal.close(payload.getTarget());
         } else if (event.getPayload() instanceof AjaxWizard.NewItemEvent) {
             AjaxWizard.NewItemEvent<T> newItemEvent = AjaxWizard.NewItemEvent.class.cast(event.getPayload());
             Optional<AjaxRequestTarget> target = newItemEvent.getTarget();
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectTemplateWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectTemplateWizardBuilder.java
index 709626917a..9fb5370199 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectTemplateWizardBuilder.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyObjectTemplateWizardBuilder.java
@@ -54,9 +54,8 @@ public class AnyObjectTemplateWizardBuilder extends AnyObjectWizardBuilder
         } else {
             AnyObjectTO anyObjectTO = new AnyObjectTO();
             anyObjectTO.setType(anyType);
-            if (templatable instanceof RealmTO) {
-                anyObjectTO.setRealm(
-                        String.format("'%s'", RealmsUtils.getFullPath(RealmTO.class.cast(templatable).getFullPath())));
+            if (templatable instanceof final RealmTO realmTO) {
+                anyObjectTO.setRealm(String.format("'%s'", RealmsUtils.getFullPath(realmTO.getFullPath())));
             }
             setItem(new AnyObjectWrapper(anyObjectTO));
         }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyWizardBuilder.java
index bf322d4b3b..f62a8870de 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyWizardBuilder.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AnyWizardBuilder.java
@@ -103,16 +103,15 @@ public abstract class AnyWizardBuilder<A extends AnyTO> extends AbstractAnyWizar
         // optional details panel step
         addOptionalDetailsPanel(modelObject).ifPresent(wizardModel::add);
 
-        if ((this instanceof GroupWizardBuilder)
-                && (modelObject.getInnerObject() instanceof GroupTO)
-                && (formLayoutInfo instanceof GroupFormLayoutInfo)) {
+        if (this instanceof GroupWizardBuilder
+                && modelObject instanceof final GroupWrapper gw
+                && formLayoutInfo instanceof final GroupFormLayoutInfo groupFormLayoutInfo) {
 
-            GroupFormLayoutInfo groupFormLayoutInfo = GroupFormLayoutInfo.class.cast(formLayoutInfo);
             if (groupFormLayoutInfo.isOwnership()) {
-                wizardModel.add(new Ownership(GroupWrapper.class.cast(modelObject), pageRef));
+                wizardModel.add(new Ownership(gw, pageRef));
             }
             if (groupFormLayoutInfo.isDynamicMemberships()) {
-                wizardModel.add(new DynamicMemberships(GroupWrapper.class.cast(modelObject), pageRef));
+                wizardModel.add(new DynamicMemberships(gw, pageRef));
             }
         }
 
@@ -141,10 +140,10 @@ public abstract class AnyWizardBuilder<A extends AnyTO> extends AbstractAnyWizar
         }
 
         // role panel step (just available for users)
-        if ((this instanceof UserWizardBuilder)
+        if (this instanceof UserWizardBuilder
                 && modelObject instanceof final UserWrapper userWrapper
-                && formLayoutInfo instanceof UserFormLayoutInfo
-                && UserFormLayoutInfo.class.cast(formLayoutInfo).isRoles()) {
+                && formLayoutInfo instanceof final UserFormLayoutInfo ufli
+                && ufli.isRoles()) {
 
             wizardModel.add(new Roles(userWrapper));
         }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Details.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Details.java
index 2cf541b1c0..478d1d1fae 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Details.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Details.java
@@ -92,14 +92,14 @@ public class Details<T extends AnyTO> extends WizardStep {
 
                 @Override
                 protected Iterator<String> getChoices(final String input) {
-                    return (pageRef.getPage() instanceof Realms
-                            ? getRealmsFromLinks(Realms.class.cast(pageRef.getPage()).getRealmChoicePanel().getLinks())
+                    return (pageRef.getPage() instanceof Realms realms
+                            ? getRealmsFromLinks(realms.getRealmChoicePanel().getLinks())
                             : (fullRealmsTree
                                     ? realmRestClient.search(RealmsUtils.buildBaseQuery())
                                     : realmRestClient.search(RealmsUtils.buildKeywordQuery(input))).getResult()).
                             stream().map(RealmTO::getFullPath).
-                        filter(fullPath -> authRealms.stream().anyMatch(
-                        authRealm -> fullPath.startsWith(authRealm))).iterator();
+                            filter(fullPath -> authRealms.stream().anyMatch(
+                            authRealm -> fullPath.startsWith(authRealm))).iterator();
                 }
             };
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupTemplateWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupTemplateWizardBuilder.java
index 64cb87af12..12036a94c9 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupTemplateWizardBuilder.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupTemplateWizardBuilder.java
@@ -52,9 +52,8 @@ public class GroupTemplateWizardBuilder extends GroupWizardBuilder implements Te
             setItem(new GroupWrapper(GroupTO.class.cast(templatable.getTemplates().get(AnyTypeKind.GROUP.name()))));
         } else {
             GroupTO groupTO = new GroupTO();
-            if (templatable instanceof RealmTO) {
-                groupTO.setRealm(
-                        String.format("'%s'", RealmsUtils.getFullPath(RealmTO.class.cast(templatable).getFullPath())));
+            if (templatable instanceof final RealmTO realmTO) {
+                groupTO.setRealm(String.format("'%s'", RealmsUtils.getFullPath(realmTO.getFullPath())));
             }
             setItem(new GroupWrapper(groupTO));
         }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupWizardBuilder.java
index 936adcc25c..83995f2726 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupWizardBuilder.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupWizardBuilder.java
@@ -90,8 +90,8 @@ public class GroupWizardBuilder extends AnyWizardBuilder<GroupTO> implements Gro
 
     @Override
     protected Serializable onApplyInternal(final AnyWrapper<GroupTO> modelObject) {
-        GroupTO updated = modelObject instanceof GroupWrapper
-                ? GroupWrapper.class.cast(modelObject).fillDynamicConditions()
+        GroupTO updated = modelObject instanceof GroupWrapper gw
+                ? gw.fillDynamicConditions()
                 : modelObject.getInnerObject();
 
         ProvisioningResult<GroupTO> result;
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Ownership.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Ownership.java
index 3a65e1d9af..2d9a8683a5 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Ownership.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Ownership.java
@@ -314,8 +314,8 @@ public class Ownership extends WizardStep implements ICondition {
 
     @Override
     public void onEvent(final IEvent<?> event) {
-        if (event.getPayload() instanceof SearchClausePanel.SearchEvent) {
-            AjaxRequestTarget target = SearchClausePanel.SearchEvent.class.cast(event.getPayload()).getTarget();
+        if (event.getPayload() instanceof SearchClausePanel.SearchEvent payload) {
+            AjaxRequestTarget target = payload.getTarget();
             if (Ownership.this.isGroupOwnership.getObject()) {
                 String fiql = SearchUtils.buildFIQL(
                         groupSearchPanel.getModel().getObject(), SyncopeClient.getGroupSearchConditionBuilder());
@@ -325,7 +325,7 @@ public class Ownership extends WizardStep implements ICondition {
                         userSearchPanel.getModel().getObject(), SyncopeClient.getUserSearchConditionBuilder());
                 userDirectoryPanel.search(fiql, target);
             }
-        } else if (event.getPayload() instanceof final AnySelectionDirectoryPanel.ItemSelection itemSelection) {
+        } else if (event.getPayload() instanceof final AnySelectionDirectoryPanel.ItemSelection<?> itemSelection) {
             AnyTO sel = itemSelection.getSelection();
             if (sel == null) {
                 wrapper.getInnerObject().setUserOwner(null);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/PlainAttrs.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/PlainAttrs.java
index af58c4ec97..bb00038281 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/PlainAttrs.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/PlainAttrs.java
@@ -61,19 +61,15 @@ public class PlainAttrs extends AbstractAttrs<PlainSchemaTO> {
 
         super(modelObject, mode, anyTypeClasses, whichPlainAttrs);
 
-        if (modelObject.getInnerObject() instanceof UserTO) {
-            fileKey = UserTO.class.cast(modelObject.getInnerObject()).getUsername();
-        } else if (modelObject.getInnerObject() instanceof GroupTO) {
-            fileKey = GroupTO.class.cast(modelObject.getInnerObject()).getName();
-        } else if (modelObject.getInnerObject() instanceof AnyObjectTO) {
-            fileKey = AnyObjectTO.class.cast(modelObject.getInnerObject()).getName();
-        }
-
-        if (modelObject instanceof UserWrapper) {
-            previousObject = UserWrapper.class.cast(modelObject).getPreviousUserTO();
-        } else {
-            previousObject = null;
-        }
+        fileKey = modelObject.getInnerObject() instanceof UserTO userTO
+                ? userTO.getUsername()
+                : modelObject.getInnerObject() instanceof GroupTO groupTO
+                ? groupTO.getName()
+                : modelObject.getInnerObject() instanceof AnyObjectTO anyObjectTO
+                ? anyObjectTO.getName()
+                : null;
+
+        previousObject = modelObject instanceof UserWrapper uw ? uw.getPreviousUserTO() : null;
 
         setTitleModel(new ResourceModel("attributes.plain"));
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java
index 833c1528e5..3d2de13162 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java
@@ -87,11 +87,11 @@ public class Relationships extends WizardStep implements ICondition {
         super();
         add(new Label("title", new ResourceModel("any.relationships")));
 
-        if (modelObject instanceof UserWrapper
-                && UserWrapper.class.cast(modelObject).getPreviousUserTO() != null
+        if (modelObject instanceof final UserWrapper uw
+                && uw.getPreviousUserTO() != null
                 && !ListUtils.isEqualList(
-                        UserWrapper.class.cast(modelObject).getInnerObject().getRelationships(),
-                        UserWrapper.class.cast(modelObject).getPreviousUserTO().getRelationships())) {
+                        uw.getInnerObject().getRelationships(),
+                        uw.getPreviousUserTO().getRelationships())) {
             add(new LabelInfo("changed", StringUtils.EMPTY));
         } else {
             add(new Label("changed", StringUtils.EMPTY));
@@ -242,24 +242,19 @@ public class Relationships extends WizardStep implements ICondition {
 
         @Override
         public void onEvent(final IEvent<?> event) {
-            if (event.getPayload() instanceof SearchClausePanel.SearchEvent) {
-                AjaxRequestTarget target =
-                        SearchClausePanel.SearchEvent.class.cast(event.getPayload()).getTarget();
+            if (event.getPayload() instanceof final SearchClausePanel.SearchEvent payload) {
                 String fiql = SearchUtils.buildFIQL(anyObjectSearchPanel.getModel().getObject(),
                         SyncopeClient.getAnyObjectSearchConditionBuilder(anyObjectSearchPanel.getAnyType()));
-                AnyDirectoryPanel.class.cast(anyObjectDirectoryPanel).search(fiql, target);
-            } else if (event.getPayload() instanceof AnySelectionDirectoryPanel.ItemSelection) {
-                AjaxRequestTarget target =
-                        AnySelectionDirectoryPanel.ItemSelection.class.cast(event.getPayload()).getTarget();
-
-                AnyTO right = AnySelectionDirectoryPanel.ItemSelection.class.cast(event.getPayload()).getSelection();
+                AnyDirectoryPanel.class.cast(anyObjectDirectoryPanel).search(fiql, payload.getTarget());
+            } else if (event.getPayload() instanceof final AnySelectionDirectoryPanel.ItemSelection<?> payload) {
+                AnyTO right = payload.getSelection();
                 rel.setOtherEndKey(right.getKey());
                 rel.setOtherEndName(AnyObjectTO.class.cast(right).getName());
 
                 anyTO.getRelationships().add(rel);
 
                 Relationships.this.addOrReplace(getViewFragment().setRenderBodyOnly(true));
-                target.add(Relationships.this);
+                payload.getTarget().add(Relationships.this);
             } else {
                 super.onEvent(event);
             }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java
index b6f5bc37ec..271a370e23 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserTemplateWizardBuilder.java
@@ -66,9 +66,8 @@ public class UserTemplateWizardBuilder extends UserWizardBuilder implements Temp
             setItem(new UserWrapper(UserTO.class.cast(templatable.getTemplates().get(AnyTypeKind.USER.name()))));
         } else {
             UserTO userTO = new UserTO();
-            if (templatable instanceof RealmTO) {
-                userTO.setRealm(
-                        String.format("'%s'", RealmsUtils.getFullPath(RealmTO.class.cast(templatable).getFullPath())));
+            if (templatable instanceof final RealmTO realmTO) {
+                userTO.setRealm(String.format("'%s'", RealmsUtils.getFullPath(realmTO.getFullPath())));
             }
             setItem(new UserWrapper(userTO));
         }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java
index b91a95f60a..6230f129a0 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java
@@ -74,8 +74,8 @@ public class UserWizardBuilder extends AnyWizardBuilder<UserTO> implements UserF
         if (inner.getKey() == null) {
             UserCR req = new UserCR();
             EntityTOUtils.toAnyCR(inner, req);
-            req.setStorePassword(modelObject instanceof UserWrapper
-                    ? UserWrapper.class.cast(modelObject).isStorePasswordInSyncope()
+            req.setStorePassword(modelObject instanceof final UserWrapper uw
+                    ? uw.isStorePasswordInSyncope()
                     : StringUtils.isNotBlank(inner.getPassword()));
 
             result = userRestClient.create(req);
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Login.html b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Login.html
index 2b8716e52e..10fa864d45 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Login.html
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/pages/Login.html
@@ -59,7 +59,6 @@ under the License.
         <img class="login-logo" src="ui-commons/img/logo-green.png" />
 
         <span wicket:id="feedback" role="alert"/>
-        <div class="bg-danger mb-3 text-white" style="padding: 5px;text-align: center;" wicket:id="exceptionMessage"/>
 
         <form class="form-signin" wicket:id="login">
           <fieldset class="form-group">
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/widgets/AlertWidget.html b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/widgets/AlertWidget.html
index f7e6f32d3e..05648f22eb 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/widgets/AlertWidget.html
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/widgets/AlertWidget.html
@@ -29,7 +29,6 @@ under the License.
       </span>
       <div class="dropdown-divider"></div>
       <a href="#" class="dropdown-item">
-        <span wicket:id="latestAlertsList"/>
       </a>
       <div class="dropdown-divider"></div>
       <a href="#" class="dropdown-item dropdown-footer" wicket:id="alertsLink"><wicket:message key="alerts.view.all"/></a>
diff --git a/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java b/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java
index 6a2065f74f..62eaaf3485 100644
--- a/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java
+++ b/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java
@@ -30,9 +30,12 @@ import com.giffing.wicket.spring.boot.starter.app.classscanner.candidates.Wicket
 import com.giffing.wicket.spring.boot.starter.configuration.extensions.core.settings.general.GeneralSettingsProperties;
 import com.giffing.wicket.spring.boot.starter.configuration.extensions.external.spring.boot.actuator.WicketEndpointRepositoryDefault;
 import java.io.IOException;
+import java.time.OffsetDateTime;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Set;
+import javax.cache.Cache;
+import javax.cache.CacheManager;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.commons.lang3.tuple.Triple;
 import org.apache.cxf.jaxrs.client.Client;
@@ -136,6 +139,13 @@ public abstract class AbstractTest {
             lookup.load();
             return lookup;
         }
+
+        @Bean
+        public CacheManager cacheManager() {
+            CacheManager cacheManager = mock(CacheManager.class);
+            when(cacheManager.createCache(anyString(), any())).thenAnswer(ic -> mock(Cache.class));
+            return cacheManager;
+        }
     }
 
     public static class TestSyncopeWebApplication extends SyncopeWebApplication {
@@ -153,12 +163,14 @@ public abstract class AbstractTest {
                 final AccessPolicyConfProvider accessPolicyConfProvider,
                 final List<PolicyTabProvider> policyTabProviders,
                 final List<UserFormFinalizer> userFormFinalizers,
-                final List<IResource> resources) {
+                final List<IResource> resources,
+                final Cache<String, OffsetDateTime> loggedoutSessionIdCache,
+                final Cache<String, OffsetDateTime> destroyedSessionIdCache) {
 
             super(props, lookup, serviceOps, resourceProvider, anyDirectoryPanelAdditionalActionsProvider,
                     anyDirectoryPanelAdditionalActionLinksProvider, anyWizardBuilderAdditionalSteps, statusProvider,
                     implementationInfoProvider, accessPolicyConfProvider, policyTabProviders, userFormFinalizers,
-                    resources);
+                    resources, loggedoutSessionIdCache, destroyedSessionIdCache);
         }
 
         public interface SyncopeServiceClient extends SyncopeService, Client {
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserRequestCycleListener.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserRequestCycleListener.java
deleted file mode 100644
index 9b5ff53794..0000000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserRequestCycleListener.java
+++ /dev/null
@@ -1,104 +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.syncope.client.enduser;
-
-import jakarta.ws.rs.BadRequestException;
-import jakarta.ws.rs.ForbiddenException;
-import jakarta.ws.rs.NotAuthorizedException;
-import jakarta.xml.ws.WebServiceException;
-import org.apache.commons.lang3.Strings;
-import org.apache.syncope.client.enduser.pages.Login;
-import org.apache.syncope.common.lib.SyncopeClientException;
-import org.apache.wicket.authorization.UnauthorizedInstantiationException;
-import org.apache.wicket.core.request.handler.PageProvider;
-import org.apache.wicket.core.request.handler.RenderPageRequestHandler;
-import org.apache.wicket.markup.html.pages.ExceptionErrorPage;
-import org.apache.wicket.protocol.http.PageExpiredException;
-import org.apache.wicket.request.IRequestHandler;
-import org.apache.wicket.request.component.IRequestablePage;
-import org.apache.wicket.request.cycle.IRequestCycleListener;
-import org.apache.wicket.request.cycle.RequestCycle;
-import org.apache.wicket.request.mapper.parameter.PageParameters;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class SyncopeEnduserRequestCycleListener implements IRequestCycleListener {
-
-    private static final Logger LOG = LoggerFactory.getLogger(SyncopeEnduserRequestCycleListener.class);
-
-    private Throwable instanceOf(final Exception e, final Class<? extends Exception> clazz) {
-        return clazz.isAssignableFrom(e.getClass())
-                ? e
-                : e.getCause() != null && clazz.isAssignableFrom(e.getCause().getClass())
-                ? e.getCause()
-                : e.getCause() != null && e.getCause().getCause() != null
-                && clazz.isAssignableFrom(e.getCause().getCause().getClass())
-                ? e.getCause().getCause()
-                : null;
-    }
-
-    @Override
-    public IRequestHandler onException(final RequestCycle cycle, final Exception e) {
-        LOG.error("Exception found", e);
-
-        PageParameters errorParameters = new PageParameters();
-
-        IRequestablePage errorPage;
-        if (instanceOf(e, UnauthorizedInstantiationException.class) != null) {
-            errorParameters.add("errorMessage", SyncopeEnduserSession.Error.AUTHORIZATION.fallback());
-            errorPage = new Login(errorParameters);
-        } else if (instanceOf(e, NotAuthorizedException.class) != null) {
-            if (Strings.CI.contains(instanceOf(e, NotAuthorizedException.class).getMessage(), "expired")) {
-                errorParameters.add("errorMessage", SyncopeEnduserSession.Error.SESSION_EXPIRED.fallback());
-            } else {
-                errorParameters.add("errorMessage", SyncopeEnduserSession.Error.AUTHORIZATION.fallback());
-            }
-            errorPage = new Login(errorParameters);
-        } else if (instanceOf(e, PageExpiredException.class) != null || !SyncopeEnduserSession.get().isSignedIn()) {
-            errorParameters.add("errorMessage", SyncopeEnduserSession.Error.SESSION_EXPIRED.fallback());
-            errorPage = new Login(errorParameters);
-        } else if (instanceOf(e, BadRequestException.class) != null
-                || instanceOf(e, WebServiceException.class) != null
-                || instanceOf(e, SyncopeClientException.class) != null) {
-
-            errorParameters.add("errorMessage", SyncopeEnduserSession.Error.REST.fallback());
-            errorPage = new Login(errorParameters);
-        } else {
-            Throwable cause = instanceOf(e, ForbiddenException.class);
-            if (cause == null) {
-                // redirect to default Wicket error page
-                errorPage = new ExceptionErrorPage(e, null);
-            } else {
-                errorParameters.add("errorMessage", cause.getMessage());
-                errorPage = new Login(errorParameters);
-            }
-        }
-
-        if (errorPage instanceof Login) {
-            try {
-                SyncopeEnduserSession.get().invalidate();
-            } catch (Throwable t) {
-                // ignore
-                LOG.debug("Unexpected error while forcing logout after error", t);
-            }
-        }
-
-        return new RenderPageRequestHandler(new PageProvider(errorPage));
-    }
-}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
index 7324c59758..e21b847534 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
@@ -39,6 +39,7 @@ import org.apache.syncope.client.enduser.pages.SelfConfirmPasswordReset;
 import org.apache.syncope.client.enduser.panels.Sidebar;
 import org.apache.syncope.client.lib.SyncopeAnonymousClient;
 import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
+import org.apache.syncope.client.ui.commons.BaseLogin;
 import org.apache.syncope.client.ui.commons.BaseWebApplication;
 import org.apache.syncope.client.ui.commons.SyncopeUIRequestCycleListener;
 import org.apache.syncope.client.ui.commons.annotations.Resource;
@@ -58,11 +59,9 @@ import org.apache.wicket.protocol.http.servlet.XForwardedRequestWrapperFactory;
 import org.apache.wicket.request.Request;
 import org.apache.wicket.request.Response;
 import org.apache.wicket.request.component.IRequestableComponent;
-import org.apache.wicket.request.component.IRequestablePage;
 import org.apache.wicket.request.cycle.IRequestCycleListener;
 import org.apache.wicket.request.cycle.RequestCycle;
 import org.apache.wicket.request.http.WebResponse;
-import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.apache.wicket.request.resource.IResource;
 import org.apache.wicket.request.resource.ResourceReference;
 import org.slf4j.Logger;
@@ -120,8 +119,8 @@ public class SyncopeWebApplication extends WicketBootSecuredWebApplication imple
             }
 
             @Override
-            protected IRequestablePage getErrorPage(final PageParameters errorParameters) {
-                return new Login(errorParameters);
+            protected Class<? extends BaseLogin> getErrorPageClass() {
+                return Login.class;
             }
         };
     }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset.java
index 2b6d3d4dc4..e3663ee547 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset.java
@@ -37,6 +37,7 @@ import org.apache.wicket.markup.html.form.Form;
 import org.apache.wicket.markup.html.form.StatelessForm;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.wicketstuff.kendo.ui.widget.notification.Notification;
 
 public class SelfConfirmPasswordReset extends BasePage {
 
@@ -54,7 +55,8 @@ public class SelfConfirmPasswordReset extends BasePage {
             LOG.error("No token parameter found in the request url");
 
             PageParameters homeParameters = new PageParameters();
-            homeParameters.add("errorMessage", getString("self.confirm.pwd.reset.error.empty"));
+            homeParameters.add(Constants.NOTIFICATION_MSG_PARAM, getString("self.confirm.pwd.reset.error.empty"));
+            homeParameters.add(Constants.NOTIFICATION_LEVEL_PARAM, Notification.ERROR);
             setResponsePage(getApplication().getHomePage(), homeParameters);
         }
 
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserSelfFormPanel.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserSelfFormPanel.java
index bbf634bccb..b1ee519431 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserSelfFormPanel.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserSelfFormPanel.java
@@ -84,8 +84,8 @@ public class UserSelfFormPanel extends UserFormPanel {
                 // create and set page parameters according to provisioning result
                 UserCR req = new UserCR();
                 EntityTOUtils.toAnyCR(userTO, req);
-                req.setStorePassword(form.getModelObject() instanceof UserWrapper
-                        ? UserWrapper.class.cast(form.getModelObject()).isStorePasswordInSyncope()
+                req.setStorePassword(form.getModelObject() instanceof final UserWrapper uw
+                        ? uw.isStorePasswordInSyncope()
                         : StringUtils.isNotBlank(userTO.getPassword()));
                 // perform request and pass propagation statuses to SelfResult page
                 ProvisioningResult<UserTO> provisioningResult = userSelfRestClient.create(req);
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Groups.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Groups.java
index c767c3544d..547bead8ac 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Groups.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Groups.java
@@ -78,11 +78,12 @@ public class Groups extends Panel {
         // ------------------
         // insert changed label if needed
         // ------------------
-        if (modelObject instanceof UserWrapper
-                && UserWrapper.class.cast(modelObject).getPreviousUserTO() != null
+        if (modelObject instanceof final UserWrapper uw
+                && uw.getPreviousUserTO() != null
                 && !ListUtils.isEqualList(
-                        UserWrapper.class.cast(modelObject).getInnerObject().getMemberships(),
-                        UserWrapper.class.cast(modelObject).getPreviousUserTO().getMemberships())) {
+                        uw.getInnerObject().getMemberships(),
+                        uw.getPreviousUserTO().getMemberships())) {
+
             groupsContainer.add(new LabelInfo("changed", StringUtils.EMPTY));
         } else {
             groupsContainer.add(new Label("changed", StringUtils.EMPTY));
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Resources.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Resources.java
index 1897edb42c..d4d9d90f9d 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Resources.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Resources.java
@@ -42,10 +42,9 @@ public class Resources extends Panel {
         super(id);
         final T entityTO = modelObject.getInnerObject();
 
-        if (modelObject instanceof UserWrapper
-                && UserWrapper.class.cast(modelObject).getPreviousUserTO() != null
-                && !modelObject.getInnerObject().getResources().equals(
-                        UserWrapper.class.cast(modelObject).getPreviousUserTO().getResources())) {
+        if (modelObject instanceof final UserWrapper uw
+                && uw.getPreviousUserTO() != null
+                && !modelObject.getInnerObject().getResources().equals(uw.getPreviousUserTO().getResources())) {
 
             add(new LabelInfo("changed", StringUtils.EMPTY));
         } else {
@@ -55,22 +54,21 @@ public class Resources extends Panel {
         this.setOutputMarkupId(true);
         this.available = new ListModel<>(List.of());
 
-        add(new AjaxPalettePanel.Builder<String>().build("resources",
-            new PropertyModel<>(entityTO, "resources") {
+        add(new AjaxPalettePanel.Builder<String>().build("resources", new PropertyModel<>(entityTO, "resources") {
 
-                private static final long serialVersionUID = 3799387950428254072L;
+            private static final long serialVersionUID = 3799387950428254072L;
 
-                @Override
-                public List<String> getObject() {
-                    return new ArrayList<>(entityTO.getResources());
-                }
+            @Override
+            public List<String> getObject() {
+                return new ArrayList<>(entityTO.getResources());
+            }
 
-                @Override
-                public void setObject(final List<String> object) {
-                    entityTO.getResources().clear();
-                    entityTO.getResources().addAll(object);
-                }
-            }, available).hideLabel().setOutputMarkupId(true));
+            @Override
+            public void setObject(final List<String> object) {
+                entityTO.getResources().clear();
+                entityTO.getResources().addAll(object);
+            }
+        }, available).hideLabel().setOutputMarkupId(true));
     }
 
     @Override
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Login.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Login.html
index a50dc3c633..3f72fce27a 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Login.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Login.html
@@ -58,7 +58,6 @@ under the License.
         <img class="login-logo" src="ui-commons/img/logo-green.png" />
 
         <span wicket:id="feedback" role="alert"/>
-        <div class="bg-red mb-3 text-white" style="padding: 5px;text-align: center;" wicket:id="exceptionMessage"/>
 
         <form class="form-signin" wicket:id="login">
           <fieldset class="form-group">
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AccessTokenDataBinder.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AccessTokenDataBinder.java
index a24bc035b7..fe8d194bac 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AccessTokenDataBinder.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AccessTokenDataBinder.java
@@ -27,9 +27,6 @@ import org.apache.syncope.core.persistence.api.entity.AccessToken;
 
 public interface AccessTokenDataBinder {
 
-    Pair<String, OffsetDateTime> generateJWT(
-            String tokenId, String subject, long duration, Map<String, Object> claims);
-
     Pair<String, OffsetDateTime> create(
             Optional<String> key, String subject, Map<String, Object> claims, byte[] authorities, boolean replace);
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java
index aa8a42f68e..af1d29f948 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AccessTokenDataBinderImpl.java
@@ -72,8 +72,7 @@ public class AccessTokenDataBinderImpl implements AccessTokenDataBinder {
         this.credentialChecker = credentialChecker;
     }
 
-    @Override
-    public Pair<String, OffsetDateTime> generateJWT(
+    protected Pair<String, OffsetDateTime> generateJWT(
             final String tokenId,
             final String subject,
             final long duration,
@@ -106,7 +105,7 @@ public class AccessTokenDataBinderImpl implements AccessTokenDataBinder {
         return Pair.of(jwt.serialize(), expiration);
     }
 
-    private AccessToken replace(
+    protected AccessToken replace(
             final String subject,
             final Map<String, Object> claims,
             final byte[] authorities,
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/UserRequestDirectoryPanel.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/UserRequestDirectoryPanel.java
index 8e6bc4aee0..8f340bad16 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/UserRequestDirectoryPanel.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/UserRequestDirectoryPanel.java
@@ -167,8 +167,7 @@ public class UserRequestDirectoryPanel
 
     @Override
     public void onEvent(final IEvent<?> event) {
-        if (event.getPayload() instanceof UserRequestSearchEvent) {
-            UserRequestSearchEvent payload = UserRequestSearchEvent.class.cast(event.getPayload());
+        if (event.getPayload() instanceof final UserRequestSearchEvent payload) {
             keyword = payload.getKeyword();
 
             updateResultTable(payload.getTarget());
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/UserRequestFormDirectoryPanel.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/UserRequestFormDirectoryPanel.java
index ddce479d77..c628c6a90f 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/UserRequestFormDirectoryPanel.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/UserRequestFormDirectoryPanel.java
@@ -109,8 +109,8 @@ public class UserRequestFormDirectoryPanel
             updateResultTable(target);
 
             Serializable widget = SyncopeConsoleSession.get().getAttribute(UserRequestFormsWidget.class.getName());
-            if (widget instanceof final UserRequestFormsWidget components) {
-                components.refreshLatestAlerts(target);
+            if (widget instanceof final UserRequestFormsWidget urfw) {
+                urfw.refresh(target);
             }
 
             if (actionTogglePanel.isVisibleInHierarchy()) {
@@ -316,8 +316,7 @@ public class UserRequestFormDirectoryPanel
 
     @Override
     public void onEvent(final IEvent<?> event) {
-        if (event.getPayload() instanceof UserRequestSearchEvent) {
-            UserRequestSearchEvent payload = UserRequestSearchEvent.class.cast(event.getPayload());
+        if (event.getPayload() instanceof final UserRequestSearchEvent payload) {
             keyword = payload.getKeyword();
 
             updateResultTable(payload.getTarget());
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/UserRequestFormsWidget.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/UserRequestFormsWidget.java
index 1b5a2c4a51..f8dd5515d3 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/UserRequestFormsWidget.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/widgets/UserRequestFormsWidget.java
@@ -20,100 +20,38 @@ package org.apache.syncope.client.console.widgets;
 
 import de.agilecoders.wicket.core.markup.html.bootstrap.image.Icon;
 import de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesome5IconType;
-import java.time.Duration;
-import java.time.temporal.ChronoUnit;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.pages.UserRequests;
 import org.apache.syncope.client.console.rest.UserRequestRestClient;
-import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior;
 import org.apache.syncope.client.ui.commons.annotations.ExtWidget;
-import org.apache.syncope.common.lib.to.UserRequestForm;
 import org.apache.syncope.common.lib.types.FlowableEntitlement;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
-import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
 import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.markup.html.link.AbstractLink;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.util.ListModel;
 import org.apache.wicket.spring.injection.annot.SpringBean;
 
 @ExtWidget(priority = 10)
-public class UserRequestFormsWidget extends ExtAlertWidget<UserRequestForm> {
+public class UserRequestFormsWidget extends ExtAlertWidget {
 
     private static final long serialVersionUID = 7667120094526529934L;
 
     @SpringBean
     protected UserRequestRestClient userRequestRestClient;
 
-    protected final List<UserRequestForm> lastForms = new ArrayList<>();
+    protected final boolean authorized;
 
     public UserRequestFormsWidget(final String id, final PageReference pageRef) {
         super(id, pageRef);
-        setOutputMarkupId(true);
-
-        latestAlertsList.add(new IndicatorAjaxTimerBehavior(Duration.of(30, ChronoUnit.SECONDS)) {
-
-            private static final long serialVersionUID = 7298597675929755960L;
-
-            @Override
-            protected void onTimer(final AjaxRequestTarget target) {
-                if (!latestAlerts.getObject().equals(lastForms)) {
-                    refreshLatestAlerts(target);
-                }
-            }
-        });
-    }
-
-    public final void refreshLatestAlerts(final AjaxRequestTarget target) {
-        latestAlerts.getObject().clear();
-        latestAlerts.getObject().addAll(lastForms);
-
-        long latestAlertSize = getLatestAlertsSize();
-        linkAlertsNumber.setDefaultModelObject(latestAlertSize);
-        target.add(linkAlertsNumber);
-
-        headerAlertsNumber.setDefaultModelObject(latestAlertSize);
-        target.add(headerAlertsNumber);
-
-        target.add(latestAlertsList);
-
-        lastForms.clear();
-        lastForms.addAll(latestAlerts.getObject());
+        authorized = SyncopeConsoleSession.get().owns(FlowableEntitlement.USER_REQUEST_FORM_LIST);
     }
 
     @Override
     protected long getLatestAlertsSize() {
-        return SyncopeConsoleSession.get().owns(FlowableEntitlement.USER_REQUEST_FORM_LIST)
-                ? userRequestRestClient.countForms(null)
-                : 0L;
-    }
-
-    @Override
-    protected IModel<List<UserRequestForm>> getLatestAlerts() {
-        return new ListModel<>() {
-
-            private static final long serialVersionUID = -2583290457773357445L;
-
-            @Override
-            public List<UserRequestForm> getObject() {
-                List<UserRequestForm> updatedForms;
-                if (SyncopeConsoleSession.get().owns(FlowableEntitlement.USER_REQUEST_FORM_LIST)) {
-                    updatedForms = userRequestRestClient.listForms(
-                            null, 1, MAX_SIZE, new SortParam<>("createTime", true));
-                } else {
-                    updatedForms = Collections.emptyList();
-                }
-
-                return updatedForms;
-            }
-        };
+        return authorized ? userRequestRestClient.countForms(null) : 0L;
     }
 
     @Override
@@ -128,4 +66,15 @@ public class UserRequestFormsWidget extends ExtAlertWidget<UserRequestForm> {
     protected Icon getIcon(final String iconid) {
         return new Icon(iconid, FontAwesome5IconType.handshake_r);
     }
+
+    public void refresh(final AjaxRequestTarget target) {
+        long latestAlterts = getLatestAlertsSize();
+        if (!String.valueOf(latestAlterts).equals(linkAlertsNumber.getDefaultModelObjectAsString())) {
+            linkAlertsNumber.setDefaultModelObject(latestAlterts);
+            target.add(linkAlertsNumber);
+
+            headerAlertsNumber.setDefaultModelObject(latestAlterts);
+            target.add(headerAlertsNumber);
+        }
+    }
 }
diff --git a/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClientLogin.java b/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClientLogin.java
index 5e277f8c3b..4bfa4a8b53 100644
--- a/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClientLogin.java
+++ b/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClientLogin.java
@@ -27,6 +27,7 @@ import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.wicketstuff.kendo.ui.widget.notification.Notification;
 
 public class OIDCClientLogin extends WebPage {
 
@@ -43,9 +44,10 @@ public class OIDCClientLogin extends WebPage {
         if (StringUtils.isBlank(token)) {
             LOG.error("No JWT found, redirecting to default greeter");
 
-            PageParameters params = new PageParameters();
-            params.add("errorMessage", OIDC_ACCESS_ERROR);
-            setResponsePage(Login.class, params);
+            PageParameters loginParameters = new PageParameters();
+            loginParameters.add(Constants.NOTIFICATION_MSG_PARAM, OIDC_ACCESS_ERROR);
+            loginParameters.add(Constants.NOTIFICATION_LEVEL_PARAM, Notification.ERROR);
+            setResponsePage(Login.class, loginParameters);
         }
 
         IAuthenticationStrategy strategy = getApplication().getSecuritySettings().getAuthenticationStrategy();
@@ -60,10 +62,12 @@ public class OIDCClientLogin extends WebPage {
             continueToOriginalDestination();
             setResponsePage(getApplication().getHomePage());
         } else {
-            PageParameters params = new PageParameters();
-            params.add("errorMessage", OIDC_ACCESS_ERROR);
-            setResponsePage(Login.class, params);
+            PageParameters loginParameters = new PageParameters();
+            loginParameters.add(Constants.NOTIFICATION_MSG_PARAM, OIDC_ACCESS_ERROR);
+            loginParameters.add(Constants.NOTIFICATION_LEVEL_PARAM, Notification.ERROR);
+            setResponsePage(Login.class, loginParameters);
         }
+
         strategy.remove();
     }
 }
diff --git a/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClientSelfReg.java b/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClientSelfReg.java
index db29dce4ab..7f2fbfe390 100644
--- a/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClientSelfReg.java
+++ b/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/pages/OIDCClientSelfReg.java
@@ -18,8 +18,10 @@
  */
 package org.apache.syncope.client.console.pages;
 
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.wicketstuff.kendo.ui.widget.notification.Notification;
 
 public class OIDCClientSelfReg extends WebPage {
 
@@ -31,8 +33,9 @@ public class OIDCClientSelfReg extends WebPage {
     public OIDCClientSelfReg(final PageParameters parameters) {
         super(parameters);
 
-        PageParameters params = new PageParameters();
-        params.add("errorMessage", OIDC_ACCESS_ERROR);
-        setResponsePage(Login.class, params);
+        PageParameters loginParameters = new PageParameters();
+        loginParameters.add(Constants.NOTIFICATION_MSG_PARAM, OIDC_ACCESS_ERROR);
+        loginParameters.add(Constants.NOTIFICATION_LEVEL_PARAM, Notification.ERROR);
+        setResponsePage(Login.class, loginParameters);
     }
 }
diff --git a/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/panels/OIDCProvidersDirectoryPanel.java b/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/panels/OIDCProvidersDirectoryPanel.java
index c7d9e92723..bef7272b81 100644
--- a/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/panels/OIDCProvidersDirectoryPanel.java
+++ b/ext/oidcc4ui/client-console/src/main/java/org/apache/syncope/client/console/panels/OIDCProvidersDirectoryPanel.java
@@ -243,8 +243,7 @@ public class OIDCProvidersDirectoryPanel extends DirectoryPanel<
     public void onEvent(final IEvent<?> event) {
         super.onEvent(event);
 
-        if (event.getPayload() instanceof AjaxWizard.NewItemEvent) {
-            AjaxWizard.NewItemEvent<?> newItemEvent = AjaxWizard.NewItemEvent.class.cast(event.getPayload());
+        if (event.getPayload() instanceof AjaxWizard.NewItemEvent<?> newItemEvent) {
             WizardModalPanel<?> modalPanel = newItemEvent.getModalPanel();
 
             if (newItemEvent instanceof AjaxWizard.NewItemActionEvent && modalPanel != null) {
diff --git a/ext/oidcc4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/pages/OIDCClientLogin.java b/ext/oidcc4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/pages/OIDCClientLogin.java
index 60e207a094..7a73dc343a 100644
--- a/ext/oidcc4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/pages/OIDCClientLogin.java
+++ b/ext/oidcc4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/pages/OIDCClientLogin.java
@@ -27,6 +27,7 @@ import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.wicketstuff.kendo.ui.widget.notification.Notification;
 
 public class OIDCClientLogin extends WebPage {
 
@@ -43,9 +44,10 @@ public class OIDCClientLogin extends WebPage {
         if (StringUtils.isBlank(token)) {
             LOG.error("No JWT found, redirecting to default greeter");
 
-            PageParameters params = new PageParameters();
-            params.add("errorMessage", OIDC_ACCESS_ERROR);
-            setResponsePage(Login.class, params);
+            PageParameters loginParameters = new PageParameters();
+            loginParameters.add(Constants.NOTIFICATION_MSG_PARAM, OIDC_ACCESS_ERROR);
+            loginParameters.add(Constants.NOTIFICATION_LEVEL_PARAM, Notification.ERROR);
+            setResponsePage(Login.class, loginParameters);
         }
 
         IAuthenticationStrategy strategy = getApplication().getSecuritySettings().getAuthenticationStrategy();
@@ -62,10 +64,12 @@ public class OIDCClientLogin extends WebPage {
             continueToOriginalDestination();
             setResponsePage(getApplication().getHomePage());
         } else {
-            PageParameters params = new PageParameters();
-            params.add("errorMessage", OIDC_ACCESS_ERROR);
-            setResponsePage(Login.class, params);
+            PageParameters loginParameters = new PageParameters();
+            loginParameters.add(Constants.NOTIFICATION_MSG_PARAM, OIDC_ACCESS_ERROR);
+            loginParameters.add(Constants.NOTIFICATION_LEVEL_PARAM, Notification.ERROR);
+            setResponsePage(Login.class, loginParameters);
         }
+
         strategy.remove();
     }
 }
diff --git a/ext/saml2sp4ui/client-console/src/main/java/org/apache/syncope/client/console/pages/SAML2SPLogin.java b/ext/saml2sp4ui/client-console/src/main/java/org/apache/syncope/client/console/pages/SAML2SPLogin.java
index d62bb72d75..92073c1f65 100644
--- a/ext/saml2sp4ui/client-console/src/main/java/org/apache/syncope/client/console/pages/SAML2SPLogin.java
+++ b/ext/saml2sp4ui/client-console/src/main/java/org/apache/syncope/client/console/pages/SAML2SPLogin.java
@@ -27,6 +27,7 @@ import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.wicketstuff.kendo.ui.widget.notification.Notification;
 
 public class SAML2SPLogin extends WebPage {
 
@@ -43,9 +44,10 @@ public class SAML2SPLogin extends WebPage {
         if (StringUtils.isBlank(token)) {
             LOG.error("No JWT found, redirecting to default greeter");
 
-            PageParameters params = new PageParameters();
-            params.add("errorMessage", SAML_ACCESS_ERROR);
-            setResponsePage(Login.class, params);
+            PageParameters loginParameters = new PageParameters();
+            loginParameters.add(Constants.NOTIFICATION_MSG_PARAM, SAML_ACCESS_ERROR);
+            loginParameters.add(Constants.NOTIFICATION_LEVEL_PARAM, Notification.ERROR);
+            setResponsePage(Login.class, loginParameters);
         }
 
         IAuthenticationStrategy strategy = getApplication().getSecuritySettings().getAuthenticationStrategy();
@@ -60,10 +62,12 @@ public class SAML2SPLogin extends WebPage {
             continueToOriginalDestination();
             setResponsePage(getApplication().getHomePage());
         } else {
-            PageParameters params = new PageParameters();
-            params.add("errorMessage", SAML_ACCESS_ERROR);
-            setResponsePage(Login.class, params);
+            PageParameters loginParameters = new PageParameters();
+            loginParameters.add(Constants.NOTIFICATION_MSG_PARAM, SAML_ACCESS_ERROR);
+            loginParameters.add(Constants.NOTIFICATION_LEVEL_PARAM, Notification.ERROR);
+            setResponsePage(Login.class, loginParameters);
         }
+
         strategy.remove();
     }
 }
diff --git a/ext/saml2sp4ui/client-console/src/main/java/org/apache/syncope/client/console/pages/SAML2SPSelfReg.java b/ext/saml2sp4ui/client-console/src/main/java/org/apache/syncope/client/console/pages/SAML2SPSelfReg.java
index c7633e8212..f46f343367 100644
--- a/ext/saml2sp4ui/client-console/src/main/java/org/apache/syncope/client/console/pages/SAML2SPSelfReg.java
+++ b/ext/saml2sp4ui/client-console/src/main/java/org/apache/syncope/client/console/pages/SAML2SPSelfReg.java
@@ -18,21 +18,24 @@
  */
 package org.apache.syncope.client.console.pages;
 
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.wicketstuff.kendo.ui.widget.notification.Notification;
 
 public class SAML2SPSelfReg extends WebPage {
 
     private static final long serialVersionUID = -4330637558823990359L;
 
-    private static final String SAML_ACCESS_ERROR = 
+    private static final String SAML_ACCESS_ERROR =
             "SAML 2.0 error - Admin Console does not support Self Registration";
 
     public SAML2SPSelfReg(final PageParameters parameters) {
         super(parameters);
 
-        PageParameters params = new PageParameters();
-        params.add("errorMessage", SAML_ACCESS_ERROR);
-        setResponsePage(Login.class, params);
+        PageParameters loginParameters = new PageParameters();
+        loginParameters.add(Constants.NOTIFICATION_MSG_PARAM, SAML_ACCESS_ERROR);
+        loginParameters.add(Constants.NOTIFICATION_LEVEL_PARAM, Notification.ERROR);
+        setResponsePage(Login.class, loginParameters);
     }
 }
diff --git a/ext/saml2sp4ui/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java b/ext/saml2sp4ui/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java
index d7f0557a9e..2f191c4a3a 100644
--- a/ext/saml2sp4ui/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java
+++ b/ext/saml2sp4ui/client-console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPsDirectoryPanel.java
@@ -281,8 +281,7 @@ public class SAML2IdPsDirectoryPanel extends DirectoryPanel<
     public void onEvent(final IEvent<?> event) {
         super.onEvent(event);
 
-        if (event.getPayload() instanceof AjaxWizard.NewItemEvent) {
-            AjaxWizard.NewItemEvent<?> newItemEvent = AjaxWizard.NewItemEvent.class.cast(event.getPayload());
+        if (event.getPayload() instanceof final AjaxWizard.NewItemEvent<?> newItemEvent) {
             WizardModalPanel<?> modalPanel = newItemEvent.getModalPanel();
 
             if (newItemEvent instanceof AjaxWizard.NewItemActionEvent && modalPanel != null) {
diff --git a/ext/saml2sp4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/pages/SAML2SPLogin.java b/ext/saml2sp4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/pages/SAML2SPLogin.java
index d8aa14bdad..abe8ff1bcd 100644
--- a/ext/saml2sp4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/pages/SAML2SPLogin.java
+++ b/ext/saml2sp4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/pages/SAML2SPLogin.java
@@ -27,6 +27,7 @@ import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.wicketstuff.kendo.ui.widget.notification.Notification;
 
 public class SAML2SPLogin extends WebPage {
 
@@ -43,9 +44,10 @@ public class SAML2SPLogin extends WebPage {
         if (StringUtils.isBlank(token)) {
             LOG.error("No JWT found, redirecting to default greeter");
 
-            PageParameters params = new PageParameters();
-            params.add("errorMessage", SAML_ACCESS_ERROR);
-            setResponsePage(Login.class, params);
+            PageParameters loginParameters = new PageParameters();
+            loginParameters.add(Constants.NOTIFICATION_MSG_PARAM, SAML_ACCESS_ERROR);
+            loginParameters.add(Constants.NOTIFICATION_LEVEL_PARAM, Notification.ERROR);
+            setResponsePage(Login.class, loginParameters);
         }
 
         IAuthenticationStrategy strategy = getApplication().getSecuritySettings().getAuthenticationStrategy();
@@ -62,10 +64,12 @@ public class SAML2SPLogin extends WebPage {
             continueToOriginalDestination();
             setResponsePage(getApplication().getHomePage());
         } else {
-            PageParameters params = new PageParameters();
-            params.add("errorMessage", SAML_ACCESS_ERROR);
-            setResponsePage(Login.class, params);
+            PageParameters loginParameters = new PageParameters();
+            loginParameters.add(Constants.NOTIFICATION_MSG_PARAM, SAML_ACCESS_ERROR);
+            loginParameters.add(Constants.NOTIFICATION_LEVEL_PARAM, Notification.ERROR);
+            setResponsePage(Login.class, loginParameters);
         }
+
         strategy.remove();
     }
 }
diff --git a/fit/core-reference/pom.xml b/fit/core-reference/pom.xml
index 6d000f31c7..de966b3aa6 100644
--- a/fit/core-reference/pom.xml
+++ b/fit/core-reference/pom.xml
@@ -231,6 +231,11 @@ under the License.
       <artifactId>jetty-client</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter</artifactId>
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractConsoleITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractConsoleITCase.java
index dc598e4d17..3e0590e089 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractConsoleITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractConsoleITCase.java
@@ -19,6 +19,10 @@
 package org.apache.syncope.fit.console;
 
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import com.giffing.wicket.spring.boot.context.extensions.WicketApplicationInitConfiguration;
 import com.giffing.wicket.spring.boot.context.extensions.boot.actuator.WicketEndpointRepository;
@@ -30,6 +34,8 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Properties;
 import java.util.Set;
+import javax.cache.Cache;
+import javax.cache.CacheManager;
 import org.apache.syncope.client.console.AMConsoleContext;
 import org.apache.syncope.client.console.ConsoleProperties;
 import org.apache.syncope.client.console.FlowableConsoleContext;
@@ -122,6 +128,13 @@ public abstract class AbstractConsoleITCase extends AbstractUIITCase {
             System.setProperty(LoggingSystem.SYSTEM_PROPERTY, LoggingSystem.NONE);
             return LoggingSystem.get(getClass().getClassLoader());
         }
+
+        @Bean
+        public CacheManager cacheManager() {
+            CacheManager cacheManager = mock(CacheManager.class);
+            when(cacheManager.createCache(anyString(), any())).thenAnswer(ic -> mock(Cache.class));
+            return cacheManager;
+        }
     }
 
     @BeforeAll
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/NotificationTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/NotificationTaskITCase.java
index 05ddbae7c0..5ef79526be 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/NotificationTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/NotificationTaskITCase.java
@@ -149,7 +149,11 @@ public class NotificationTaskITCase extends AbstractTaskITCase {
         assertTrue(taskTO.isExecuted());
         assertFalse(taskTO.getExecutions().isEmpty());
 
-        TASK_SERVICE.delete(TaskType.NOTIFICATION, taskTO.getKey());
+        try {
+            TASK_SERVICE.delete(TaskType.NOTIFICATION, taskTO.getKey());
+        } catch (Exception e) {
+            // ignore
+        }
     }
 
     @Test
diff --git a/pom.xml b/pom.xml
index 8768d175d0..70199fe987 100644
--- a/pom.xml
+++ b/pom.xml
@@ -433,7 +433,7 @@ under the License.
 
     <cxf.version>4.1.3</cxf.version>
     <bouncycastle.version>1.82</bouncycastle.version>
-    <nimbus-jose-jwt.version>10.5</nimbus-jose-jwt.version>
+    <nimbus-jose-jwt.version>10.6</nimbus-jose-jwt.version>
 
     <spring-boot.version>3.5.7</spring-boot.version>
     <spring-cloud-gateway.version>4.3.2</spring-cloud-gateway.version>
@@ -533,7 +533,7 @@ under the License.
     <docker.neo4j.version>5.26</docker.neo4j.version>
 
     <jdbc.postgresql.version>42.7.8</jdbc.postgresql.version>
-    <jdbc.mysql.version>9.4.0</jdbc.mysql.version>
+    <jdbc.mysql.version>9.5.0</jdbc.mysql.version>
     <jdbc.mariadb.version>3.5.6</jdbc.mariadb.version>
     <jdbc.oracle.version>23.26.0.0.0</jdbc.oracle.version>