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

commit 3be90d61a89842e6991f63d0f96c2f3421a126bf
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Tue Apr 18 13:56:44 2023 +0200

    [SYNCOPE-1752] Refactoring Realms storage and management (#442)
---
 .../clientapps/ClientAppModalPanelBuilder.java     |  16 +-
 .../client/console/status/ReconTaskPanel.java      |  19 ++-
 .../wizards/resources/ConnectorDetailsPanel.java   |  19 ++-
 .../syncope/client/console/ConsoleProperties.java  |  14 +-
 .../client/console/SyncopeConsoleSession.java      |   2 +-
 .../client/console/SyncopeWebApplication.java      |  15 ++
 .../client/console/commons/RealmsUtils.java        |  32 +---
 .../syncope/client/console/pages/BasePage.java     |   2 +-
 .../client/console/panels/RealmChoicePanel.java    |  78 +++++-----
 .../client/console/rest/RealmRestClient.java       |   4 -
 .../console/tasks/SchedTaskWizardBuilder.java      |  23 +--
 .../client/console/wizards/any/Details.java        |  30 ++--
 .../console/wizards/role/RoleWizardBuilder.java    |  48 ++++--
 .../console/src/main/resources/console.properties  |   2 +
 .../client/enduser/panels/any/UserDetails.java     |  20 +--
 .../client/enduser/rest/AbstractAnyRestClient.java |   1 -
 .../client/enduser/rest/GroupRestClient.java       |   2 +-
 .../client/enduser/rest/RealmRestClient.java       |  36 -----
 .../client/enduser/rest/SchemaRestClient.java      |   2 +-
 .../enduser/rest/SecurityQuestionRestClient.java   |   1 -
 .../common/lib/types/IdRepoEntitlement.java        |   2 +-
 .../syncope/common/rest/api/beans/RealmQuery.java  |  18 +--
 .../common/rest/api/service/RealmService.java      |  19 +--
 .../src/main/resources/defaultContent.xml          |   2 +-
 .../org/apache/syncope/core/logic/RealmLogic.java  |  73 +++------
 .../core/rest/cxf/service/RealmServiceImpl.java    |  14 +-
 .../syncope/core/persistence/api/dao/RealmDAO.java |  10 +-
 .../resources/domains/jpa-json/MasterContent.xml   |   4 +-
 .../src/main/resources/myjson/indexes.xml          |   3 +
 .../src/main/resources/ojson/indexes.xml           |   3 +
 .../src/main/resources/pgjsonb/indexes.xml         |   4 +
 .../src/test/resources/domains/MasterContent.xml   |  14 +-
 .../jpa/content/XMLContentExporter.java            |  10 +-
 .../core/persistence/jpa/dao/JPAAnySearchDAO.java  |  18 +--
 .../core/persistence/jpa/dao/JPARealmDAO.java      | 171 +++++++++++++--------
 .../core/persistence/jpa/dao/JPATaskDAO.java       |   2 +-
 .../core/persistence/jpa/entity/JPARealm.java      |  14 +-
 .../src/main/resources/domains/MasterContent.xml   |   4 +-
 .../persistence-jpa/src/main/resources/indexes.xml |   3 +
 .../src/main/resources/oracle_indexes.xml          |   3 +
 .../persistence/jpa/inner/MultitenancyTest.java    |   6 +-
 .../core/persistence/jpa/inner/RealmTest.java      |  10 +-
 .../jpa/outer/XMLContentExporterTest.java          |   6 +-
 .../src/test/resources/domains/MasterContent.xml   |  14 +-
 .../src/test/resources/domains/TwoContent.xml      |   4 +-
 .../provisioning/java/pushpull/InboundMatcher.java |   5 +-
 .../java/pushpull/PushJobDelegate.java             |   3 +-
 .../client/ElasticsearchClientContext.java         |   2 +-
 .../jpa/dao/ElasticsearchAnySearchDAO.java         |   2 +-
 .../jpa/dao/ElasticsearchAnySearchDAOTest.java     |   3 +-
 .../syncope/fit/core/reference/TestCommand.java    |   3 +-
 .../org/apache/syncope/fit/AbstractITCase.java     |   4 +-
 .../org/apache/syncope/fit/core/CommandITCase.java |   4 +-
 .../org/apache/syncope/fit/core/MacroITCase.java   |   4 +-
 .../org/apache/syncope/fit/core/RealmITCase.java   |  89 ++++-------
 .../org/apache/syncope/fit/core/SearchITCase.java  |   9 +-
 pom.xml                                            |  16 +-
 .../reference-guide/concepts/entitlements.adoc     |   4 +-
 .../asciidoc/reference-guide/concepts/roles.adoc   |   4 +-
 59 files changed, 466 insertions(+), 483 deletions(-)

diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java
index 36f1620a89..a80a05ec5b 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java
@@ -29,6 +29,7 @@ import java.util.stream.Stream;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.commons.RealmsUtils;
 import org.apache.syncope.client.console.panels.AbstractModalPanel;
 import org.apache.syncope.client.console.rest.ClientAppRestClient;
@@ -49,7 +50,6 @@ import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
 import org.apache.syncope.client.ui.commons.panels.WizardModalPanel;
 import org.apache.syncope.client.ui.commons.wizards.AbstractModalPanelBuilder;
 import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
-import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.policy.PolicyTO;
 import org.apache.syncope.common.lib.to.ClientAppTO;
 import org.apache.syncope.common.lib.to.RealmTO;
@@ -154,10 +154,10 @@ public class ClientAppModalPanelBuilder<T extends 
ClientAppTO> extends AbstractM
 
             List<Component> fields = new ArrayList<>();
 
-            boolean isSearchEnabled = RealmsUtils.isSearchEnabled();
+            boolean fullRealmsTree = 
SyncopeWebApplication.get().fullRealmsTree();
             AutoCompleteSettings settings = new AutoCompleteSettings();
-            settings.setShowCompleteListOnFocusGain(!isSearchEnabled);
-            settings.setShowListOnEmptyInput(!isSearchEnabled);
+            settings.setShowCompleteListOnFocusGain(fullRealmsTree);
+            settings.setShowListOnEmptyInput(fullRealmsTree);
             AjaxSearchFieldPanel realm = new AjaxSearchFieldPanel(
                     "field", "realm", new PropertyModel<>(clientAppTO, 
"realm"), settings) {
 
@@ -165,11 +165,9 @@ public class ClientAppModalPanelBuilder<T extends 
ClientAppTO> extends AbstractM
 
                 @Override
                 protected Iterator<String> getChoices(final String input) {
-                    return (isSearchEnabled
-                            ? 
RealmRestClient.search(RealmsUtils.buildQuery(input)).getResult()
-                            : 
RealmRestClient.list(SyncopeConstants.ROOT_REALM)).
-                            stream().filter(realm -> 
SyncopeConsoleSession.get().getAuthRealms().stream().
-                            anyMatch(authRealm -> 
realm.getFullPath().startsWith(authRealm))).
+                    return RealmRestClient.search(fullRealmsTree
+                            ? RealmsUtils.buildRootQuery()
+                            : 
RealmsUtils.buildKeywordQuery(input)).getResult().stream().
                             
map(RealmTO::getFullPath).collect(Collectors.toList()).iterator();
                 }
             };
diff --git 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/status/ReconTaskPanel.java
 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/status/ReconTaskPanel.java
index 1b7e76f473..07b33950ff 100644
--- 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/status/ReconTaskPanel.java
+++ 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/status/ReconTaskPanel.java
@@ -18,12 +18,12 @@
  */
 package org.apache.syncope.client.console.status;
 
-import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.commons.RealmsUtils;
 import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.panels.MultilevelPanel;
@@ -72,7 +72,7 @@ public class ReconTaskPanel extends 
MultilevelPanel.SecondLevel {
         @Override
         protected List<String> load() {
             return 
ImplementationRestClient.list(IdMImplementationType.PULL_ACTIONS).stream().
-                
map(ImplementationTO::getKey).sorted().collect(Collectors.toList());
+                    
map(ImplementationTO::getKey).sorted().collect(Collectors.toList());
         }
     };
 
@@ -83,7 +83,7 @@ public class ReconTaskPanel extends 
MultilevelPanel.SecondLevel {
         @Override
         protected List<String> load() {
             return 
ImplementationRestClient.list(IdMImplementationType.PUSH_ACTIONS).stream().
-                
map(ImplementationTO::getKey).sorted().collect(Collectors.toList());
+                    
map(ImplementationTO::getKey).sorted().collect(Collectors.toList());
         }
     };
 
@@ -116,10 +116,10 @@ public class ReconTaskPanel extends 
MultilevelPanel.SecondLevel {
             form.add(new Label("realm", ""));
             form.add(new Label("remediation", ""));
         } else {
-            boolean isSearchEnabled = RealmsUtils.isSearchEnabled();
+            boolean fullRealmsTree = 
SyncopeWebApplication.get().fullRealmsTree();
             AutoCompleteSettings settings = new AutoCompleteSettings();
-            settings.setShowCompleteListOnFocusGain(!isSearchEnabled);
-            settings.setShowListOnEmptyInput(!isSearchEnabled);
+            settings.setShowCompleteListOnFocusGain(fullRealmsTree);
+            settings.setShowListOnEmptyInput(fullRealmsTree);
 
             AjaxSearchFieldPanel realm = new AjaxSearchFieldPanel(
                     "realm", "destinationRealm", new PropertyModel<>(taskTO, 
"destinationRealm"), settings) {
@@ -129,11 +129,10 @@ public class ReconTaskPanel extends 
MultilevelPanel.SecondLevel {
                 @Override
                 protected Iterator<String> getChoices(final String input) {
                     return (RealmsUtils.checkInput(input)
-                            ? (isSearchEnabled
-                                    ? 
RealmRestClient.search(RealmsUtils.buildQuery(input)).getResult()
-                                    : 
RealmRestClient.list(SyncopeConstants.ROOT_REALM))
+                            ? (RealmRestClient.search(fullRealmsTree
+                                    ? RealmsUtils.buildRootQuery()
+                                    : 
RealmsUtils.buildKeywordQuery(input)).getResult())
                             : List.<RealmTO>of()).stream().
-                            sorted(Comparator.comparing(RealmTO::getName)).
                             
map(RealmTO::getFullPath).collect(Collectors.toList()).iterator();
                 }
             };
diff --git 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorDetailsPanel.java
 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorDetailsPanel.java
index f769716a09..f0e05c8c6b 100644
--- 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorDetailsPanel.java
+++ 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorDetailsPanel.java
@@ -23,7 +23,7 @@ import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.tuple.Pair;
-import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.commons.RealmsUtils;
 import org.apache.syncope.client.console.rest.RealmRestClient;
 import 
org.apache.syncope.client.console.wicket.markup.html.form.AjaxSearchFieldPanel;
@@ -32,7 +32,6 @@ import 
org.apache.syncope.client.ui.commons.ajax.form.IndicatorAjaxFormComponent
 import 
org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
 import 
org.apache.syncope.client.ui.commons.markup.html.form.AjaxSpinnerFieldPanel;
 import 
org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
-import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.ConnIdBundle;
 import org.apache.syncope.common.lib.to.ConnInstanceTO;
 import org.apache.syncope.common.lib.to.ConnPoolConfTO;
@@ -50,11 +49,11 @@ public class ConnectorDetailsPanel extends WizardStep {
         super();
         setOutputMarkupId(true);
 
-        boolean isSearchEnabled = RealmsUtils.isSearchEnabled();
+        boolean fullRealmsTree = SyncopeWebApplication.get().fullRealmsTree();
 
         AutoCompleteSettings settings = new AutoCompleteSettings();
-        settings.setShowCompleteListOnFocusGain(!isSearchEnabled);
-        settings.setShowListOnEmptyInput(!isSearchEnabled);
+        settings.setShowCompleteListOnFocusGain(fullRealmsTree);
+        settings.setShowListOnEmptyInput(fullRealmsTree);
 
         AjaxSearchFieldPanel realm = new AjaxSearchFieldPanel(
                 "adminRealm", "adminRealm", new 
PropertyModel<>(connInstanceTO, "adminRealm"), settings) {
@@ -63,11 +62,11 @@ public class ConnectorDetailsPanel extends WizardStep {
 
             @Override
             protected Iterator<String> getChoices(final String input) {
-                return (isSearchEnabled
-                        ? 
RealmRestClient.search(RealmsUtils.buildQuery(input)).getResult()
-                        : RealmRestClient.list(SyncopeConstants.ROOT_REALM)).
-                        stream().filter(realm -> 
SyncopeConsoleSession.get().getAuthRealms().stream().
-                        anyMatch(authRealm -> 
realm.getFullPath().startsWith(authRealm))).
+                return (RealmsUtils.checkInput(input)
+                        ? (RealmRestClient.search(fullRealmsTree
+                                ? RealmsUtils.buildRootQuery()
+                                : 
RealmsUtils.buildKeywordQuery(input)).getResult())
+                        : List.<RealmTO>of()).stream().
                         
map(RealmTO::getFullPath).collect(Collectors.toList()).iterator();
             }
         };
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/ConsoleProperties.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/ConsoleProperties.java
index 3594c84be6..14a8a1ed0a 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/ConsoleProperties.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/ConsoleProperties.java
@@ -72,6 +72,8 @@ public class ConsoleProperties extends CommonUIProperties {
 
     private String defaultAnyPanelClass = AnyPanel.class.getName();
 
+    private int realmsFullTreeThreshold = 20;
+
     private final Topology topology = new Topology();
 
     @Override
@@ -84,6 +86,10 @@ public class ConsoleProperties extends CommonUIProperties {
         this.adminUser = adminUser;
     }
 
+    public Map<String, Class<? extends BasePage>> getPage() {
+        return page;
+    }
+
     public String getDefaultAnyPanelClass() {
         return defaultAnyPanelClass;
     }
@@ -92,8 +98,12 @@ public class ConsoleProperties extends CommonUIProperties {
         this.defaultAnyPanelClass = defaultAnyPanelClass;
     }
 
-    public Map<String, Class<? extends BasePage>> getPage() {
-        return page;
+    public int getRealmsFullTreeThreshold() {
+        return realmsFullTreeThreshold;
+    }
+
+    public void setRealmsFullTreeThreshold(final int realmsFullTreeThreshold) {
+        this.realmsFullTreeThreshold = realmsFullTreeThreshold;
     }
 
     public Topology getTopology() {
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 a2c79b82dd..a80fa45cd4 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
@@ -286,7 +286,7 @@ public class SyncopeConsoleSession extends 
AuthenticatedWebSession implements Ba
     }
 
     public List<String> getSearchableRealms() {
-        Set<String> roots = auth.get(IdRepoEntitlement.REALM_LIST);
+        Set<String> roots = auth.get(IdRepoEntitlement.REALM_SEARCH);
         return CollectionUtils.isEmpty(roots)
                 ? List.of()
                 : roots.stream().sorted().collect(Collectors.toList());
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 3da6a87661..a5de408b57 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
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.client.console;
 
+
 import 
com.giffing.wicket.spring.boot.starter.app.WicketBootSecuredWebApplication;
 import de.agilecoders.wicket.core.Bootstrap;
 import de.agilecoders.wicket.core.settings.BootstrapSettings;
@@ -33,6 +34,7 @@ import 
org.apache.syncope.client.console.commons.AnyWizardBuilderAdditionalSteps
 import org.apache.syncope.client.console.commons.ExternalResourceProvider;
 import org.apache.syncope.client.console.commons.ImplementationInfoProvider;
 import org.apache.syncope.client.console.commons.PolicyTabProvider;
+import org.apache.syncope.client.console.commons.RealmsUtils;
 import org.apache.syncope.client.console.commons.StatusProvider;
 import org.apache.syncope.client.console.commons.VirSchemaDetailsPanelProvider;
 import 
org.apache.syncope.client.console.init.ClassPathScanImplementationLookup;
@@ -40,6 +42,7 @@ import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.pages.Dashboard;
 import org.apache.syncope.client.console.pages.Login;
 import org.apache.syncope.client.console.pages.MustChangePassword;
+import org.apache.syncope.client.console.rest.RealmRestClient;
 import org.apache.syncope.client.console.wizards.any.UserFormFinalizer;
 import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
 import org.apache.syncope.client.lib.SyncopeClient;
@@ -51,6 +54,7 @@ import org.apache.syncope.client.ui.commons.themes.AdminLTE;
 import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
 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.authroles.authentication.AbstractAuthenticatedWebSession;
 import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
@@ -303,6 +307,17 @@ public class SyncopeWebApplication extends 
WicketBootSecuredWebApplication {
         return props.getMaxUploadFileSizeMB();
     }
 
+    public boolean fullRealmsTree() {
+        if (props.getRealmsFullTreeThreshold() <= 0) {
+            return false;
+        }
+
+        RealmQuery query = RealmsUtils.buildRootQuery();
+        query.setPage(1);
+        query.setSize(0);
+        return RealmRestClient.search(query).getTotalCount() < 
props.getRealmsFullTreeThreshold();
+    }
+
     public ExternalResourceProvider getResourceProvider() {
         return resourceProvider;
     }
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/RealmsUtils.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/RealmsUtils.java
index 1553337c36..375ec10b44 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/RealmsUtils.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/RealmsUtils.java
@@ -18,21 +18,12 @@
  */
 package org.apache.syncope.client.console.commons;
 
-import java.util.List;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.client.console.SyncopeConsoleSession;
-import org.apache.syncope.client.console.rest.RealmRestClient;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.rest.api.beans.RealmQuery;
 
 public final class RealmsUtils {
 
-    public static final int REALMS_VIEW_SIZE = 20;
-
-    private RealmsUtils() {
-        // private constructor for static utility class
-    }
-
     public static String getFullPath(final String fullpath) {
         String display = fullpath;
         if (display.indexOf('@') != -1) {
@@ -41,26 +32,19 @@ public final class RealmsUtils {
         return display;
     }
 
-    public static boolean isSearchEnabled() {
-        return isSearchEnabled(SyncopeConsoleSession.get().getAuthRealms());
+    public static boolean checkInput(final String input) {
+        return StringUtils.isNotBlank(input) && !"*".equals(input);
     }
 
-    public static boolean isSearchEnabled(final List<String> realms) {
-        return realms.isEmpty()
-                ? false
-                : RealmRestClient.search(
-                        new RealmQuery.Builder().keyword(
-                                realms.contains(SyncopeConstants.ROOT_REALM)
-                                ? SyncopeConstants.ROOT_REALM
-                                : realms.get(0)).build()).
-                        getTotalCount() > REALMS_VIEW_SIZE;
+    public static RealmQuery buildKeywordQuery(final String input) {
+        return new RealmQuery.Builder().keyword(input.contains("*") ? input : 
"*" + input + "*").build();
     }
 
-    public static boolean checkInput(final String input) {
-        return StringUtils.isNotBlank(input) && !"*".equals(input);
+    public static RealmQuery buildRootQuery() {
+        return new 
RealmQuery.Builder().base(SyncopeConstants.ROOT_REALM).build();
     }
 
-    public static RealmQuery buildQuery(final String input) {
-        return new RealmQuery.Builder().keyword(input.contains("*") ? input : 
"*" + input + "*").build();
+    private RealmsUtils() {
+        // private constructor for static utility class
     }
 }
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 b60d9c7aa5..9a27bef096 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
@@ -149,7 +149,7 @@ public class BasePage extends BaseWebPage {
         body.add(liContainer);
 
         BookmarkablePageLink<? extends BasePage> link = 
BookmarkablePageLinkBuilder.build("realms", Realms.class);
-        MetaDataRoleAuthorizationStrategy.authorize(link, WebPage.RENDER, 
IdRepoEntitlement.REALM_LIST);
+        MetaDataRoleAuthorizationStrategy.authorize(link, WebPage.RENDER, 
IdRepoEntitlement.REALM_SEARCH);
 
         liContainer.add(link);
 
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RealmChoicePanel.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RealmChoicePanel.java
index 6e03afccd6..d36cb813a2 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RealmChoicePanel.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RealmChoicePanel.java
@@ -38,6 +38,7 @@ import java.util.stream.Stream;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.commons.RealmsUtils;
 import org.apache.syncope.client.console.rest.RealmRestClient;
 import 
org.apache.syncope.client.console.wicket.markup.html.WebMarkupContainerNoVeil;
@@ -47,6 +48,7 @@ import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.DynRealmTO;
 import org.apache.syncope.common.lib.to.RealmTO;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.syncope.common.rest.api.beans.RealmQuery;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.markup.html.AjaxLink;
@@ -92,16 +94,16 @@ public class RealmChoicePanel extends Panel {
 
     protected List<RealmTO> realmsChoices;
 
-    protected final boolean isSearchEnabled;
+    protected final boolean fullRealmsTree;
 
     protected final ListView<String> breadcrumb;
 
-    public RealmChoicePanel(final String id, final String initialRealm, final 
PageReference pageRef) {
+    public RealmChoicePanel(final String id, final String base, final 
PageReference pageRef) {
         super(id);
         this.pageRef = pageRef;
 
         tree = new HashMap<>();
-        isSearchEnabled = 
RealmsUtils.isSearchEnabled(SyncopeConsoleSession.get().getSearchableRealms());
+        fullRealmsTree = SyncopeWebApplication.get().fullRealmsTree();
 
         realmTree = new LoadableDetachableModel<>() {
 
@@ -111,13 +113,13 @@ public class RealmChoicePanel extends Panel {
             protected List<Pair<String, RealmTO>> load() {
                 Map<String, Pair<RealmTO, List<RealmTO>>> map = 
reloadRealmParentMap();
                 Stream<Pair<String, RealmTO>> full;
-                if (isSearchEnabled) {
-                    full = map.entrySet().stream().
-                            map(el -> Pair.of(el.getKey(), 
el.getValue().getLeft()));
-                } else {
+                if (fullRealmsTree) {
                     full = map.entrySet().stream().
                             map(el -> 
Pair.of(el.getValue().getLeft().getFullPath(), el.getValue().getKey())).
                             sorted(Comparator.comparing(Pair::getLeft));
+                } else {
+                    full = map.entrySet().stream().
+                            map(el -> Pair.of(el.getKey(), 
el.getValue().getLeft()));
                 }
                 return full.filter(realm -> 
SyncopeConsoleSession.get().getSearchableRealms().stream().anyMatch(
                         availableRealm -> 
realm.getValue().getFullPath().startsWith(availableRealm))).
@@ -147,11 +149,11 @@ public class RealmChoicePanel extends Panel {
             }
         };
 
-        RealmTO realm = 
SyncopeConsoleSession.get().getRootRealm(initialRealm).map(rootRealm -> {
+        RealmTO realm = 
SyncopeConsoleSession.get().getRootRealm(base).map(rootRealm -> {
             String rootRealmName = StringUtils.substringAfterLast(rootRealm, 
"/");
 
             List<RealmTO> realmTOs = RealmRestClient.search(
-                    
RealmsUtils.buildQuery(SyncopeConstants.ROOT_REALM.equals(rootRealm)
+                    
RealmsUtils.buildKeywordQuery(SyncopeConstants.ROOT_REALM.equals(rootRealm)
                             ? SyncopeConstants.ROOT_REALM : 
rootRealmName)).getResult();
 
             return realmTOs.stream().
@@ -187,8 +189,8 @@ public class RealmChoicePanel extends Panel {
 
                     @Override
                     public void onClick(final AjaxRequestTarget target) {
-                        RealmRestClient.list(item.getModelObject()).stream().
-                                filter(r -> 
item.getModelObject().equals(r.getFullPath())).
+                        RealmRestClient.search(
+                                new 
RealmQuery.Builder().base(item.getModelObject()).build()).getResult().stream().
                                 findFirst().ifPresent(t -> chooseRealm(t, 
target));
                     }
                 };
@@ -202,7 +204,7 @@ public class RealmChoicePanel extends Panel {
         
container.addOrReplace(breadcrumb.setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true));
         setBreadcrumb(model.getObject());
 
-        reloadRealmTree();
+        reloadRealmsTree();
     }
 
     protected void setBreadcrumb(final RealmTO realm) {
@@ -232,8 +234,28 @@ public class RealmChoicePanel extends Panel {
         send(pageRef.getPage(), Broadcast.EXACT, new ChosenRealm<>(realm, 
target));
     }
 
-    public void reloadRealmTree() {
-        if (isSearchEnabled) {
+    public void reloadRealmsTree() {
+        if (fullRealmsTree) {
+            DropDownButton realms = new DropDownButton(
+                    "realms", new ResourceModel("select", ""), new 
Model<>(FontAwesome5IconType.folder_open_r)) {
+
+                private static final long serialVersionUID = 
-5560086780455361131L;
+
+                @Override
+                protected List<AbstractLink> newSubMenuButtons(final String 
buttonMarkupId) {
+                    buildRealmLinks();
+                    return RealmChoicePanel.this.links;
+                }
+            };
+            realms.setOutputMarkupId(true);
+            realms.setAlignment(DropDownAlignmentBehavior.Alignment.RIGHT);
+            realms.setType(Buttons.Type.Menu);
+
+            MetaDataRoleAuthorizationStrategy.authorize(realms, ENABLE, 
IdRepoEntitlement.REALM_SEARCH);
+            Fragment fragment = new Fragment("realmsFragment", 
"realmsListFragment", container);
+            fragment.addOrReplace(realms);
+            container.addOrReplace(fragment);
+        } else {
             realmsChoices = buildRealmChoices();
             AutoCompleteSettings settings = new AutoCompleteSettings();
             settings.setShowCompleteListOnFocusGain(false);
@@ -294,26 +316,6 @@ public class RealmChoicePanel extends Panel {
             Fragment fragment = new Fragment("realmsFragment", 
"realmsSearchFragment", container);
             fragment.addOrReplace(searchRealms);
             container.addOrReplace(fragment);
-        } else {
-            DropDownButton realms = new DropDownButton(
-                    "realms", new ResourceModel("select", ""), new 
Model<>(FontAwesome5IconType.folder_open_r)) {
-
-                private static final long serialVersionUID = 
-5560086780455361131L;
-
-                @Override
-                protected List<AbstractLink> newSubMenuButtons(final String 
buttonMarkupId) {
-                    buildRealmLinks();
-                    return RealmChoicePanel.this.links;
-                }
-            };
-            realms.setOutputMarkupId(true);
-            realms.setAlignment(DropDownAlignmentBehavior.Alignment.RIGHT);
-            realms.setType(Buttons.Type.Menu);
-
-            MetaDataRoleAuthorizationStrategy.authorize(realms, ENABLE, 
IdRepoEntitlement.REALM_LIST);
-            Fragment fragment = new Fragment("realmsFragment", 
"realmsListFragment", container);
-            fragment.addOrReplace(realms);
-            container.addOrReplace(fragment);
         }
     }
 
@@ -419,7 +421,7 @@ public class RealmChoicePanel extends Panel {
     }
 
     public final RealmChoicePanel reloadRealmTree(final AjaxRequestTarget 
target) {
-        reloadRealmTree();
+        reloadRealmsTree();
         chooseRealm(model.getObject(), target);
         target.add(container);
         return this;
@@ -432,9 +434,9 @@ public class RealmChoicePanel extends Panel {
     }
 
     protected Map<String, Pair<RealmTO, List<RealmTO>>> reloadRealmParentMap() 
{
-        List<RealmTO> realmsToList = isSearchEnabled
-                ? 
RealmRestClient.search(RealmsUtils.buildQuery(searchQuery)).getResult()
-                : RealmRestClient.list(SyncopeConstants.ROOT_REALM);
+        List<RealmTO> realmsToList = RealmRestClient.search(fullRealmsTree
+                ? RealmsUtils.buildRootQuery()
+                : RealmsUtils.buildKeywordQuery(searchQuery)).getResult();
 
         return reloadRealmParentMap(realmsToList.stream().
                 sorted(Comparator.comparing(RealmTO::getName)).
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/RealmRestClient.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/RealmRestClient.java
index 53eed8e86e..73ccaf96a7 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/RealmRestClient.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/RealmRestClient.java
@@ -40,10 +40,6 @@ public class RealmRestClient extends BaseRestClient {
         return getService(RealmService.class).search(query);
     }
 
-    public static List<RealmTO> list(final String fullpath) {
-        return getService(RealmService.class).list(fullpath);
-    }
-
     public static List<DynRealmTO> listDynRealms() {
         return getService(DynRealmService.class).list();
     }
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java
index 0484743d49..f45cb0c40a 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java
@@ -71,12 +71,12 @@ public class SchedTaskWizardBuilder<T extends SchedTaskTO> 
extends BaseAjaxWizar
 
     private CrontabPanel crontabPanel;
 
-    private final boolean isSearchEnabled;
+    private final boolean fullRealmsTree;
 
     public SchedTaskWizardBuilder(final TaskType type, final T taskTO, final 
PageReference pageRef) {
         super(taskTO, pageRef);
         this.type = type;
-        this.isSearchEnabled = RealmsUtils.isSearchEnabled();
+        this.fullRealmsTree = SyncopeWebApplication.get().fullRealmsTree();
     }
 
     @Override
@@ -105,10 +105,11 @@ public class SchedTaskWizardBuilder<T extends 
SchedTaskTO> extends BaseAjaxWizar
         return wizardModel;
     }
 
-    private List<RealmTO> searchRealms(final String realmQuery) {
-        return isSearchEnabled
-                ? 
RealmRestClient.search(RealmsUtils.buildQuery(realmQuery)).getResult()
-                : RealmRestClient.list(SyncopeConstants.ROOT_REALM);
+    protected List<String> searchRealms(final String realmQuery) {
+        return RealmRestClient.search(fullRealmsTree
+                ? RealmsUtils.buildRootQuery()
+                : RealmsUtils.buildKeywordQuery(realmQuery)).
+                
getResult().stream().map(RealmTO::getFullPath).collect(Collectors.toList());
     }
 
     public class Profile extends WizardStep {
@@ -154,8 +155,8 @@ public class SchedTaskWizardBuilder<T extends SchedTaskTO> 
extends BaseAjaxWizar
             add(jobDelegate);
 
             AutoCompleteSettings settings = new AutoCompleteSettings();
-            settings.setShowCompleteListOnFocusGain(!isSearchEnabled);
-            settings.setShowListOnEmptyInput(!isSearchEnabled);
+            settings.setShowCompleteListOnFocusGain(fullRealmsTree);
+            settings.setShowListOnEmptyInput(fullRealmsTree);
 
             // ------------------------------
             // Only for macro tasks
@@ -172,7 +173,7 @@ public class SchedTaskWizardBuilder<T extends SchedTaskTO> 
extends BaseAjaxWizar
                 @Override
                 protected Iterator<String> getChoices(final String input) {
                     return (RealmsUtils.checkInput(input)
-                            ? 
searchRealms(input).stream().map(RealmTO::getFullPath).collect(Collectors.toList())
+                            ? searchRealms(input)
                             : List.<String>of()).iterator();
                 }
             };
@@ -247,7 +248,7 @@ public class SchedTaskWizardBuilder<T extends SchedTaskTO> 
extends BaseAjaxWizar
                 @Override
                 protected Iterator<String> getChoices(final String input) {
                     return (RealmsUtils.checkInput(input)
-                            ? 
searchRealms(input).stream().map(RealmTO::getFullPath).collect(Collectors.toList())
+                            ? searchRealms(input)
                             : List.<String>of()).iterator();
                 }
             };
@@ -283,7 +284,7 @@ public class SchedTaskWizardBuilder<T extends SchedTaskTO> 
extends BaseAjaxWizar
                 @Override
                 protected Iterator<String> getChoices(final String input) {
                     return (RealmsUtils.checkInput(input)
-                            ? 
searchRealms(input).stream().map(RealmTO::getFullPath).collect(Collectors.toList())
+                            ? searchRealms(input)
                             : List.<String>of()).iterator();
                 }
             };
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 9840b143f1..761d683568 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
@@ -18,11 +18,11 @@
  */
 package org.apache.syncope.client.console.wizards.any;
 
-import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.commons.RealmsUtils;
 import org.apache.syncope.client.console.pages.Realms;
 import org.apache.syncope.client.console.rest.RealmRestClient;
@@ -30,7 +30,6 @@ import 
org.apache.syncope.client.console.wicket.markup.html.form.AjaxSearchField
 import 
org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel;
 import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
-import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.RealmTO;
 import org.apache.wicket.Component;
@@ -70,10 +69,10 @@ public class Details<T extends AnyTO> extends WizardStep {
             AjaxTextFieldPanel.class.cast(realm).enableJexlHelp();
             fragment = new Fragment("realmsFragment", 
"realmsTemplateFragment", this);
         } else {
-            boolean isSearchEnabled = RealmsUtils.isSearchEnabled();
-            final AutoCompleteSettings settings = new AutoCompleteSettings();
-            settings.setShowCompleteListOnFocusGain(!isSearchEnabled);
-            settings.setShowListOnEmptyInput(!isSearchEnabled);
+            boolean fullRealmsTree = 
SyncopeWebApplication.get().fullRealmsTree();
+            AutoCompleteSettings settings = new AutoCompleteSettings();
+            settings.setShowCompleteListOnFocusGain(fullRealmsTree);
+            settings.setShowListOnEmptyInput(fullRealmsTree);
 
             realm = new AjaxSearchFieldPanel("destinationRealm", 
"destinationRealm",
                     new PropertyModel<>(inner, "realm"), settings) {
@@ -82,11 +81,11 @@ public class Details<T extends AnyTO> extends WizardStep {
 
                 @Override
                 protected Iterator<String> getChoices(final String input) {
-                    return (isSearchEnabled
-                            ? 
RealmRestClient.search(RealmsUtils.buildQuery(input)).getResult()
-                            : pageRef.getPage() instanceof Realms
+                    return (pageRef.getPage() instanceof Realms
                             ? 
getRealmsFromLinks(Realms.class.cast(pageRef.getPage()).getRealmChoicePanel().getLinks())
-                            : 
RealmRestClient.list(SyncopeConstants.ROOT_REALM)).
+                            : (fullRealmsTree
+                                    ? 
RealmRestClient.search(RealmsUtils.buildRootQuery())
+                                    : 
RealmRestClient.search(RealmsUtils.buildKeywordQuery(input))).getResult()).
                             stream().filter(realm -> 
authRealms.stream().anyMatch(
                             authRealm -> 
realm.getFullPath().startsWith(authRealm))).
                             
map(RealmTO::getFullPath).collect(Collectors.toList()).iterator();
@@ -112,13 +111,10 @@ public class Details<T extends AnyTO> extends WizardStep {
     }
 
     private static List<RealmTO> getRealmsFromLinks(final List<AbstractLink> 
realmLinks) {
-        List<RealmTO> realms = new ArrayList<>();
-
-        realmLinks.stream().
+        return realmLinks.stream().
                 map(Component::getDefaultModelObject).
-                filter(modelObject -> modelObject instanceof RealmTO).
-                forEachOrdered(modelObject -> realms.add((RealmTO) 
modelObject));
-
-        return realms;
+                filter(RealmTO.class::isInstance).
+                map(RealmTO.class::cast).
+                collect(Collectors.toList());
     }
 }
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/role/RoleWizardBuilder.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/role/RoleWizardBuilder.java
index d8e6478ebb..c962261343 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/role/RoleWizardBuilder.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/role/RoleWizardBuilder.java
@@ -20,26 +20,33 @@ package org.apache.syncope.client.console.wizards.role;
 
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.SyncopeWebApplication;
+import org.apache.syncope.client.console.commons.RealmsUtils;
 import org.apache.syncope.client.console.panels.search.UserSearchPanel;
 import org.apache.syncope.client.console.rest.ApplicationRestClient;
 import org.apache.syncope.client.console.rest.DynRealmRestClient;
 import org.apache.syncope.client.console.rest.RealmRestClient;
 import org.apache.syncope.client.console.rest.RoleRestClient;
+import 
org.apache.syncope.client.console.wicket.markup.html.form.AjaxSearchFieldPanel;
+import 
org.apache.syncope.client.console.wicket.markup.html.form.MultiFieldPanel;
 import org.apache.syncope.client.console.wizards.BaseAjaxWizardBuilder;
 import org.apache.syncope.client.ui.commons.Constants;
+import 
org.apache.syncope.client.ui.commons.markup.html.form.AbstractFieldPanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
 import 
org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel;
 import 
org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion;
 import org.apache.syncope.client.ui.commons.wizards.AjaxWizardBuilder;
-import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.DynRealmTO;
 import org.apache.syncope.common.lib.to.PrivilegeTO;
 import org.apache.syncope.common.lib.to.RealmTO;
 import org.apache.syncope.common.lib.to.RoleTO;
 import org.apache.wicket.PageReference;
+import 
org.apache.wicket.extensions.ajax.markup.html.autocomplete.AutoCompleteSettings;
 import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
 import org.apache.wicket.extensions.wizard.WizardModel;
 import org.apache.wicket.extensions.wizard.WizardStep;
@@ -99,7 +106,7 @@ public class RoleWizardBuilder extends 
BaseAjaxWizardBuilder<RoleWrapper> {
         return wizardModel;
     }
 
-    public class Details extends WizardStep {
+    protected class Details extends WizardStep {
 
         private static final long serialVersionUID = 5514523040031722255L;
 
@@ -129,7 +136,7 @@ public class RoleWizardBuilder extends 
BaseAjaxWizardBuilder<RoleWrapper> {
         }
     }
 
-    public static class Entitlements extends WizardStep {
+    protected static class Entitlements extends WizardStep {
 
         private static final long serialVersionUID = 5514523040031722256L;
 
@@ -155,21 +162,40 @@ public class RoleWizardBuilder extends 
BaseAjaxWizardBuilder<RoleWrapper> {
         }
     }
 
-    public static class Realms extends WizardStep {
+    protected static class Realms extends WizardStep {
 
         private static final long serialVersionUID = 5514523040031722257L;
 
+        @SuppressWarnings("unchecked")
         public Realms(final RoleTO modelObject) {
             setTitleModel(new ResourceModel("realms"));
-            add(new AjaxPalettePanel.Builder<>().build("realms",
-                    new PropertyModel<>(modelObject, "realms"),
-                    new 
ListModel<>(RealmRestClient.list(SyncopeConstants.ROOT_REALM).stream().
-                            
map(RealmTO::getFullPath).collect(Collectors.toList()))).
-                    hideLabel().setOutputMarkupId(true));
+
+            boolean fullRealmsTree = 
SyncopeWebApplication.get().fullRealmsTree();
+            AutoCompleteSettings settings = new AutoCompleteSettings();
+            settings.setShowCompleteListOnFocusGain(fullRealmsTree);
+            settings.setShowListOnEmptyInput(fullRealmsTree);
+            AbstractFieldPanel<?> realm = new AjaxSearchFieldPanel(
+                    "panel", "realm", new Model<>(), settings) {
+
+                private static final long serialVersionUID = 
-6390474600233486704L;
+
+                @Override
+                protected Iterator<String> getChoices(final String input) {
+                    return RealmRestClient.search(fullRealmsTree
+                            ? RealmsUtils.buildRootQuery()
+                            : 
RealmsUtils.buildKeywordQuery(input)).getResult().stream().
+                            
map(RealmTO::getFullPath).collect(Collectors.toList()).iterator();
+                }
+            };
+            add(new MultiFieldPanel.Builder<>(
+                    new PropertyModel<>(modelObject, "realms")).build(
+                    "realms",
+                    "realms",
+                    (FieldPanel) realm).hideLabel());
         }
     }
 
-    public static class DynRealms extends WizardStep {
+    protected static class DynRealms extends WizardStep {
 
         private static final long serialVersionUID = 6846234574424462255L;
 
@@ -183,7 +209,7 @@ public class RoleWizardBuilder extends 
BaseAjaxWizardBuilder<RoleWrapper> {
         }
     }
 
-    public static class Privileges extends WizardStep {
+    protected static class Privileges extends WizardStep {
 
         private static final long serialVersionUID = 6896014330702958579L;
 
diff --git a/client/idrepo/console/src/main/resources/console.properties 
b/client/idrepo/console/src/main/resources/console.properties
index 56a39f567f..ca80121a36 100644
--- a/client/idrepo/console/src/main/resources/console.properties
+++ b/client/idrepo/console/src/main/resources/console.properties
@@ -66,6 +66,8 @@ 
console.page.topology=org.apache.syncope.client.console.topology.Topology
 
 
console.default-any-panel-class=org.apache.syncope.client.console.panels.AnyPanel
 
+console.realms-full-tree-threshold=20
+
 console.topology.corePoolSize=10
 console.topology.maxPoolSize=20
 console.topology.queueCapacity=50
diff --git 
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/UserDetails.java
 
b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/UserDetails.java
index d68070e20c..debec15dea 100644
--- 
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/UserDetails.java
+++ 
b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/UserDetails.java
@@ -19,9 +19,8 @@
 package org.apache.syncope.client.enduser.panels.any;
 
 import 
de.agilecoders.wicket.extensions.markup.html.bootstrap.form.password.strength.PasswordStrengthBehavior;
-import java.util.stream.Collectors;
+import java.util.List;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.client.enduser.rest.RealmRestClient;
 import org.apache.syncope.client.ui.commons.ajax.markup.html.LabelInfo;
 import 
org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
 import 
org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
@@ -29,7 +28,7 @@ import 
org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel;
 import 
org.apache.syncope.client.ui.commons.markup.html.form.SyncopePasswordStrengthConfig;
 import org.apache.syncope.client.ui.commons.wizards.any.PasswordPanel;
 import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
-import org.apache.syncope.common.lib.to.RealmTO;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.markup.html.basic.Label;
@@ -41,8 +40,6 @@ public class UserDetails extends Details<UserTO> {
 
     private static final long serialVersionUID = 6592027822510220463L;
 
-    private final FieldPanel<String> realm;
-
     protected final AjaxTextFieldPanel username;
 
     protected final UserTO userTO;
@@ -68,12 +65,15 @@ public class UserDetails extends Details<UserTO> {
         // ------------------------
         // Realm
         // ------------------------
-        realm = new AjaxDropDownChoicePanel<>(
-                "destinationRealm", "destinationRealm", new 
PropertyModel<>(userTO, "realm"), false);
+        add(buildDestinationRealm());
+    }
 
-        ((AjaxDropDownChoicePanel<String>) realm).setChoices(
-                
RealmRestClient.list().stream().map(RealmTO::getFullPath).collect(Collectors.toList()));
-        add(realm);
+    protected FieldPanel<String> buildDestinationRealm() {
+        AjaxDropDownChoicePanel<String> destinationRealm = new 
AjaxDropDownChoicePanel<>(
+                "destinationRealm", "destinationRealm", new 
PropertyModel<>(userTO, "realm"), false);
+        destinationRealm.setNullValid(false);
+        destinationRealm.setChoices(List.of(SyncopeConstants.ROOT_REALM));
+        return destinationRealm;
     }
 
     protected static class EditUserPasswordPanel extends Panel {
diff --git 
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/AbstractAnyRestClient.java
 
b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/AbstractAnyRestClient.java
index b03a9cb36b..202c9c2ea0 100644
--- 
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/AbstractAnyRestClient.java
+++ 
b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/AbstractAnyRestClient.java
@@ -32,5 +32,4 @@ public abstract class AbstractAnyRestClient<TO extends AnyTO> 
extends BaseRestCl
     public TO read(final String key) {
         return getService(getAnyServiceClass()).read(key);
     }
-
 }
diff --git 
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/GroupRestClient.java
 
b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/GroupRestClient.java
index 0bf4cae887..4df6b64ce6 100644
--- 
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/GroupRestClient.java
+++ 
b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/GroupRestClient.java
@@ -33,7 +33,7 @@ import 
org.apache.syncope.common.rest.api.service.GroupService;
 import org.apache.syncope.common.rest.api.service.SyncopeService;
 
 /**
- * Console client for invoking Rest Group's services.
+ * Enduser client for invoking Rest Group's services.
  */
 public class GroupRestClient extends AbstractAnyRestClient<GroupTO> {
 
diff --git 
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/RealmRestClient.java
 
b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/RealmRestClient.java
deleted file mode 100644
index def9fd03cc..0000000000
--- 
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/RealmRestClient.java
+++ /dev/null
@@ -1,36 +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.rest;
-
-import java.util.List;
-import org.apache.syncope.common.lib.SyncopeConstants;
-import org.apache.syncope.common.lib.to.RealmTO;
-import org.apache.syncope.common.rest.api.service.RealmService;
-
-/**
- * Console client for invoking REST Realm's services.
- */
-public class RealmRestClient extends BaseRestClient {
-
-    private static final long serialVersionUID = -8549081557283519638L;
-
-    public static List<RealmTO> list() {
-        return 
getService(RealmService.class).list(SyncopeConstants.ROOT_REALM);
-    }
-}
diff --git 
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SchemaRestClient.java
 
b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SchemaRestClient.java
index 313fcd066e..c598eb540a 100644
--- 
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SchemaRestClient.java
+++ 
b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SchemaRestClient.java
@@ -35,7 +35,7 @@ import 
org.apache.syncope.common.rest.api.service.AnyTypeService;
 import org.apache.syncope.common.rest.api.service.SchemaService;
 
 /**
- * Console client for invoking rest schema services.
+ * Enduser client for invoking rest schema services.
  */
 public class SchemaRestClient extends BaseRestClient {
 
diff --git 
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SecurityQuestionRestClient.java
 
b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SecurityQuestionRestClient.java
index bf11aabd51..350f622b06 100644
--- 
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SecurityQuestionRestClient.java
+++ 
b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SecurityQuestionRestClient.java
@@ -29,5 +29,4 @@ public class SecurityQuestionRestClient extends 
BaseRestClient {
     public static List<SecurityQuestionTO> list() {
         return getService(SecurityQuestionService.class).list();
     }
-
 }
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoEntitlement.java
 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoEntitlement.java
index ed84d42e6d..1fc48fe143 100644
--- 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoEntitlement.java
+++ 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoEntitlement.java
@@ -38,7 +38,7 @@ public final class IdRepoEntitlement {
 
     public static final String DOMAIN_DELETE = "DOMAIN_DELETE";
 
-    public static final String REALM_LIST = "REALM_LIST";
+    public static final String REALM_SEARCH = "REALM_SEARCH";
 
     public static final String REALM_CREATE = "REALM_CREATE";
 
diff --git 
a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/RealmQuery.java
 
b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/RealmQuery.java
index a9ffe8d33d..0858b35680 100644
--- 
a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/RealmQuery.java
+++ 
b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/RealmQuery.java
@@ -19,31 +19,29 @@
 package org.apache.syncope.common.rest.api.beans;
 
 import jakarta.ws.rs.QueryParam;
-import java.io.Serializable;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 
-public class RealmQuery implements Serializable {
+public class RealmQuery extends AbstractQuery {
 
     private static final long serialVersionUID = -2278419397595186866L;
 
-    public static class Builder {
+    public static class Builder extends AbstractQuery.Builder<RealmQuery, 
Builder> {
 
-        private final RealmQuery instance = new RealmQuery();
+        @Override
+        protected RealmQuery newInstance() {
+            return new RealmQuery();
+        }
 
         public Builder keyword(final String keyword) {
-            instance.setKeyword(keyword);
+            getInstance().setKeyword(keyword);
             return this;
         }
 
         public Builder base(final String base) {
-            instance.setBase(base);
+            getInstance().setBase(base);
             return this;
         }
-
-        public RealmQuery build() {
-            return instance;
-        }
     }
 
     private String keyword;
diff --git 
a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/RealmService.java
 
b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/RealmService.java
index 36b5dbe285..bda03ccb4c 100644
--- 
a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/RealmService.java
+++ 
b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/RealmService.java
@@ -41,7 +41,6 @@ import jakarta.ws.rs.Produces;
 import jakarta.ws.rs.core.HttpHeaders;
 import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.Response;
-import java.util.List;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
 import org.apache.syncope.common.lib.to.RealmTO;
@@ -59,29 +58,15 @@ import org.apache.syncope.common.rest.api.beans.RealmQuery;
 public interface RealmService extends JAXRSService {
 
     /**
-     * Returns a list of existing realms matching the given query (not 
including descendant realms) and the total number
-     * of descendant realms.
+     * Returns a paged list of existing realms matching the given query.
      *
      * @param query query conditions
-     * @return list of existing realms matching the given query (not including 
descendant realms) and the total number
-     * of descedant realms
+     * @return paged list of existing realms matching the given query
      */
     @GET
-    @Path("search")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     PagedResult<RealmTO> search(@BeanParam RealmQuery query);
 
-    /**
-     * Returns realms rooted at the given path, including descendant realms.
-     *
-     * @param fullPath full path of the root realm where to read from
-     * @return realms rooted at the given path, including descendant realms
-     */
-    @GET
-    @Path("{fullPath:.*}")
-    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    List<RealmTO> list(@NotNull @PathParam("fullPath") String fullPath);
-
     /**
      * Creates a new realm under the given path.
      *
diff --git a/common/keymaster/client-api/src/main/resources/defaultContent.xml 
b/common/keymaster/client-api/src/main/resources/defaultContent.xml
index 70c0218f1a..69372e8095 100644
--- a/common/keymaster/client-api/src/main/resources/defaultContent.xml
+++ b/common/keymaster/client-api/src/main/resources/defaultContent.xml
@@ -18,7 +18,7 @@ specific language governing permissions and limitations
 under the License.
 -->
 <dataset>
-  <Realm id="ea696a4f-e77a-4ef1-be67-8f8093bc8686" name="/"/>
+  <Realm id="ea696a4f-e77a-4ef1-be67-8f8093bc8686" name="/" fullPath="/"/>
 
   <AnyType id="USER" kind="USER"/>
   <AnyTypeClass id="BaseUser"/>
diff --git 
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java 
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java
index 5c259913ee..24118f9d67 100644
--- 
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java
+++ 
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java
@@ -100,67 +100,42 @@ public class RealmLogic extends 
AbstractTransactionalLogic<RealmTO> {
         this.taskExecutor = taskExecutor;
     }
 
-    @PreAuthorize("hasRole('" + IdRepoEntitlement.REALM_LIST + "')")
+    @PreAuthorize("isAuthenticated()")
     @Transactional(readOnly = true)
-    public Pair<Integer, List<RealmTO>> search(final String keyword, final 
String base) {
-        Realm baseRealm = base == null ? realmDAO.getRoot() : 
realmDAO.findByFullPath(base);
-        if (baseRealm == null) {
-            LOG.error("Could not find realm '" + base + "'");
-
-            throw new NotFoundException(base);
-        }
+    public Pair<Integer, List<RealmTO>> search(
+            final String keyword,
+            final String base,
+            final int page,
+            final int size) {
 
-        Set<String> roots = 
AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.REALM_LIST).stream().
-                filter(auth -> 
auth.startsWith(baseRealm.getFullPath())).collect(Collectors.toSet());
+        Realm baseRealm = Optional.ofNullable(base == null ? 
realmDAO.getRoot() : realmDAO.findByFullPath(base)).
+                orElseThrow(() -> new NotFoundException(base));
 
-        Set<Realm> match = realmDAO.findMatching(keyword).stream().
-                filter(realm -> roots.stream().anyMatch(root -> 
realm.getFullPath().startsWith(root))).
-                collect(Collectors.toSet());
+        int count = realmDAO.countDescendants(baseRealm.getFullPath(), 
keyword);
 
-        int descendants = Math.toIntExact(
-                match.stream().flatMap(realm -> 
realmDAO.findDescendants(realm).stream()).distinct().count());
+        List<Realm> result = realmDAO.findDescendants(baseRealm.getFullPath(), 
keyword, page, size);
 
         return Pair.of(
-                descendants,
-                match.stream().map(realm -> binder.getRealmTO(realm, true)).
+                count,
+                result.stream().map(realm -> binder.getRealmTO(
+                realm,
+                
AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.REALM_SEARCH).stream().
+                        anyMatch(auth -> 
realm.getFullPath().startsWith(auth)))).
                         sorted(Comparator.comparing(RealmTO::getFullPath)).
                         collect(Collectors.toList()));
     }
 
-    @PreAuthorize("isAuthenticated()")
-    @Transactional(readOnly = true)
-    public List<RealmTO> list(final String fullPath) {
-        Realm realm = realmDAO.findByFullPath(fullPath);
-        if (realm == null) {
-            LOG.error("Could not find realm '" + fullPath + '\'');
-
-            throw new NotFoundException(fullPath);
-        }
-
-        boolean admin = 
AuthContextUtils.getAuthorizations().keySet().contains(IdRepoEntitlement.REALM_LIST);
-        return realmDAO.findDescendants(realm).stream().
-                map(descendant -> binder.getRealmTO(descendant, 
admin)).collect(Collectors.toList());
-    }
-
     @PreAuthorize("hasRole('" + IdRepoEntitlement.REALM_CREATE + "')")
     public ProvisioningResult<RealmTO> create(final String parentPath, final 
RealmTO realmTO) {
         Realm parent;
         if (StringUtils.isBlank(realmTO.getParent())) {
-            parent = realmDAO.findByFullPath(parentPath);
-            if (parent == null) {
-                LOG.error("Could not find parent realm " + parentPath);
-
-                throw new NotFoundException(parentPath);
-            }
+            parent = Optional.ofNullable(realmDAO.findByFullPath(parentPath)).
+                    orElseThrow(() -> new NotFoundException(parentPath));
 
             realmTO.setParent(parent.getFullPath());
         } else {
-            parent = realmDAO.find(realmTO.getParent());
-            if (parent == null) {
-                LOG.error("Could not find parent realm " + 
realmTO.getParent());
-
-                throw new NotFoundException(realmTO.getParent());
-            }
+            parent = Optional.ofNullable(realmDAO.find(realmTO.getParent())).
+                    orElseThrow(() -> new 
NotFoundException(realmTO.getParent()));
 
             if (!parent.getFullPath().equals(parentPath)) {
                 SyncopeClientException sce = 
SyncopeClientException.build(ClientExceptionType.InvalidPath);
@@ -190,12 +165,8 @@ public class RealmLogic extends 
AbstractTransactionalLogic<RealmTO> {
 
     @PreAuthorize("hasRole('" + IdRepoEntitlement.REALM_UPDATE + "')")
     public ProvisioningResult<RealmTO> update(final RealmTO realmTO) {
-        Realm realm = realmDAO.findByFullPath(realmTO.getFullPath());
-        if (realm == null) {
-            LOG.error("Could not find realm '" + realmTO.getFullPath() + '\'');
-
-            throw new NotFoundException(realmTO.getFullPath());
-        }
+        Realm realm = 
Optional.ofNullable(realmDAO.findByFullPath(realmTO.getFullPath())).
+                orElseThrow(() -> new 
NotFoundException(realmTO.getFullPath()));
 
         Map<Pair<String, String>, Set<Attribute>> beforeAttrs = 
propagationManager.prepareAttrs(realm);
 
@@ -219,7 +190,7 @@ public class RealmLogic extends 
AbstractTransactionalLogic<RealmTO> {
     @PreAuthorize("hasRole('" + IdRepoEntitlement.REALM_DELETE + "')")
     public ProvisioningResult<RealmTO> delete(final String fullPath) {
         Realm realm = Optional.ofNullable(realmDAO.findByFullPath(fullPath)).
-                orElseThrow(() -> new NotFoundException("Realm " + fullPath));
+                orElseThrow(() -> new NotFoundException(fullPath));
 
         if (!realmDAO.findChildren(realm).isEmpty()) {
             throw 
SyncopeClientException.build(ClientExceptionType.RealmContains);
diff --git 
a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RealmServiceImpl.java
 
b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RealmServiceImpl.java
index 2cdbbab006..6cebbee6d2 100644
--- 
a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RealmServiceImpl.java
+++ 
b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RealmServiceImpl.java
@@ -21,6 +21,7 @@ package org.apache.syncope.core.rest.cxf.service;
 import jakarta.ws.rs.core.Response;
 import java.net.URI;
 import java.util.List;
+import java.util.Optional;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.SyncopeConstants;
@@ -44,17 +45,14 @@ public class RealmServiceImpl extends AbstractService 
implements RealmService {
 
     @Override
     public PagedResult<RealmTO> search(final RealmQuery query) {
-        String keyword = query.getKeyword() == null ? null : 
query.getKeyword().replace('*', '%');
-
-        Pair<Integer, List<RealmTO>> result = logic.search(keyword, 
query.getBase());
+        Pair<Integer, List<RealmTO>> result = logic.search(
+                Optional.ofNullable(query.getKeyword()).map(k -> 
k.replace('*', '%')).orElse(null),
+                query.getBase(),
+                query.getPage(),
+                query.getSize());
         return buildPagedResult(result.getRight(), 1, 
result.getRight().size(), result.getLeft());
     }
 
-    @Override
-    public List<RealmTO> list(final String fullPath) {
-        return logic.list(StringUtils.prependIfMissing(fullPath, 
SyncopeConstants.ROOT_REALM));
-    }
-
     @Override
     public Response create(final String parentPath, final RealmTO realmTO) {
         ProvisioningResult<RealmTO> created =
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RealmDAO.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RealmDAO.java
index 7a3b5cbaa8..e05eb840b5 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RealmDAO.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RealmDAO.java
@@ -41,7 +41,9 @@ public interface RealmDAO extends DAO<Realm> {
 
     List<Realm> findByResource(ExternalResource resource);
 
-    List<Realm> findMatching(String keyword);
+    int countDescendants(String base, String keyword);
+
+    List<Realm> findDescendants(String base, String keyword, int page, int 
itemsPerPage);
 
     <T extends Policy> List<Realm> findByPolicy(T policy);
 
@@ -51,13 +53,7 @@ public interface RealmDAO extends DAO<Realm> {
 
     List<Realm> findChildren(Realm realm);
 
-    List<Realm> findDescendants(Realm realm);
-
-    List<Realm> findAll();
-
     Realm save(Realm realm);
 
     void delete(Realm realm);
-
-    void delete(String key);
 }
diff --git 
a/core/persistence-jpa-json/src/main/resources/domains/jpa-json/MasterContent.xml
 
b/core/persistence-jpa-json/src/main/resources/domains/jpa-json/MasterContent.xml
index f8369fb457..56c5c74801 100644
--- 
a/core/persistence-jpa-json/src/main/resources/domains/jpa-json/MasterContent.xml
+++ 
b/core/persistence-jpa-json/src/main/resources/domains/jpa-json/MasterContent.xml
@@ -18,7 +18,7 @@ specific language governing permissions and limitations
 under the License.
 -->
 <dataset>
-  <Realm id="ea696a4f-e77a-4ef1-be67-8f8093bc8686" name="/"/>  
+  <Realm id="ea696a4f-e77a-4ef1-be67-8f8093bc8686" name="/" fullPath="/"/>  
 
   <AnyType id="USER" kind="USER"/>
   <AnyTypeClass id="BaseUser"/>
@@ -104,5 +104,5 @@ we are happy to inform you that the password request was 
successfully executed f
                 sender="[email protected]" subject="Password Reset 
successful" template_id="confirmPasswordReset" 
                 traceLevel="FAILURES" 
events='["[CUSTOM]:[]:[]:[confirmPasswordReset]:[SUCCESS]"]'/> 
 
-  <SyncopeRole id="GROUP_OWNER" 
entitlements='["USER_SEARCH","USER_READ","USER_CREATE","USER_UPDATE","USER_DELETE","ANYTYPECLASS_READ","ANYTYPE_LIST","ANYTYPECLASS_LIST","RELATIONSHIPTYPE_LIST","ANYTYPE_READ","REALM_LIST","GROUP_SEARCH","GROUP_READ","GROUP_UPDATE","GROUP_DELETE"]'/>
+  <SyncopeRole id="GROUP_OWNER" 
entitlements='["USER_SEARCH","USER_READ","USER_CREATE","USER_UPDATE","USER_DELETE","ANYTYPECLASS_READ","ANYTYPE_LIST","ANYTYPECLASS_LIST","RELATIONSHIPTYPE_LIST","ANYTYPE_READ","REALM_SEARCH","GROUP_SEARCH","GROUP_READ","GROUP_UPDATE","GROUP_DELETE"]'/>
 </dataset>
diff --git a/core/persistence-jpa-json/src/main/resources/myjson/indexes.xml 
b/core/persistence-jpa-json/src/main/resources/myjson/indexes.xml
index 0fd0c62b4e..64e0d7a675 100644
--- a/core/persistence-jpa-json/src/main/resources/myjson/indexes.xml
+++ b/core/persistence-jpa-json/src/main/resources/myjson/indexes.xml
@@ -21,6 +21,9 @@ under the License.
 <properties>
   <comment>Additional indexes (in respect to JPA's)</comment>
 
+  <entry key="Realm_parent_id">CREATE INDEX Realm_parent_id ON 
Realm(parent_id)</entry>
+  <entry key="Realm_fullPath">CREATE INDEX Realm_fullPath ON 
Realm(fullPath)</entry>
+
   <entry key="SyncopeUser_realm_id">CREATE INDEX SyncopeUser_realm_id ON 
SyncopeUser(realm_id)</entry>
   <entry key="SyncopeUser_username">CREATE UNIQUE INDEX SyncopeUser_username 
ON SyncopeUser(username)</entry>
 
diff --git a/core/persistence-jpa-json/src/main/resources/ojson/indexes.xml 
b/core/persistence-jpa-json/src/main/resources/ojson/indexes.xml
index ae3f571336..b50d063222 100644
--- a/core/persistence-jpa-json/src/main/resources/ojson/indexes.xml
+++ b/core/persistence-jpa-json/src/main/resources/ojson/indexes.xml
@@ -21,6 +21,9 @@ under the License.
 <properties>
   <comment>Additional indexes (in respect to JPA's)</comment>
 
+  <entry key="Realm_parent_id">CREATE INDEX Realm_parent_id ON 
Realm(parent_id)</entry>
+  <entry key="Realm_fullPath">CREATE INDEX Realm_fullPath ON 
Realm(fullPath)</entry>
+
   <entry key="SyncopeUser_realm_id">CREATE INDEX SyncopeUser_realm_id ON 
SyncopeUser(realm_id)</entry>
   <entry key="SyncopeUser_lower_username">CREATE INDEX 
SyncopeUser_lower_username ON SyncopeUser(LOWER(username))</entry>
 
diff --git a/core/persistence-jpa-json/src/main/resources/pgjsonb/indexes.xml 
b/core/persistence-jpa-json/src/main/resources/pgjsonb/indexes.xml
index ee64b6477b..f93e8ac8b7 100644
--- a/core/persistence-jpa-json/src/main/resources/pgjsonb/indexes.xml
+++ b/core/persistence-jpa-json/src/main/resources/pgjsonb/indexes.xml
@@ -21,6 +21,10 @@ under the License.
 <properties>
   <comment>Additional indexes (in respect to JPA's)</comment>
 
+  <entry key="Realm_parent_id">CREATE INDEX Realm_parent_id ON 
Realm(parent_id)</entry>
+  <entry key="Realm_fullPath">CREATE INDEX Realm_fullPath ON 
Realm(fullPath)</entry>
+  <entry key="Realm_fullPath_startsWith">CREATE INDEX 
Realm_fullPath_startsWith ON Realm USING GIN (to_tsvector('english', 
fullPath))</entry>
+
   <entry key="SyncopeUser_realm_id">CREATE INDEX SyncopeUser_realm_id ON 
SyncopeUser(realm_id)</entry>
   <entry key="SyncopeUser_username">CREATE UNIQUE INDEX SyncopeUser_username 
ON SyncopeUser(username)</entry>
   <entry key="SyncopeUser_lower_username">CREATE INDEX 
SyncopeUser_lower_username ON SyncopeUser(LOWER(username))</entry>
diff --git 
a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml 
b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
index d061c253f4..df3293b84c 100644
--- a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
@@ -248,12 +248,14 @@ under the License.
   <PlainSchema id="location" type="String" anyTypeClass_id="minimal printer"
                mandatoryCondition="false" multivalue="0" uniqueConstraint="0" 
readonly="0"/>
 
-  <Realm id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28" name="/" 
passwordPolicy_id="986d1236-3ac5-4a19-810c-5ab21d79cba1"/>
-  <Realm id="722f3d84-9c2b-4525-8f6e-e4b82c55a36c" name="odd" 
-         parent_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28" 
accountPolicy_id="06e2ed52-6966-44aa-a177-a0ca7434201f"/>
-  <Realm id="c5b75db1-fce7-470f-b780-3b9934d82a9d" name="even" 
+  <Realm id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28" name="/"  fullPath="/"
+         passwordPolicy_id="986d1236-3ac5-4a19-810c-5ab21d79cba1"/>
+  <Realm id="722f3d84-9c2b-4525-8f6e-e4b82c55a36c" name="odd" fullPath="/odd" 
+         parent_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28"
+         accountPolicy_id="06e2ed52-6966-44aa-a177-a0ca7434201f"/>
+  <Realm id="c5b75db1-fce7-470f-b780-3b9934d82a9d" name="even" 
fullPath="/even" 
          parent_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28"/>
-  <Realm id="0679e069-7355-4b20-bd11-a5a0a5453c7c" name="two" 
+  <Realm id="0679e069-7355-4b20-bd11-a5a0a5453c7c" name="two" 
fullPath="/even/two"
          parent_id="c5b75db1-fce7-470f-b780-3b9934d82a9d"
          accountPolicy_id="20ab5a8c-4b0c-432c-b957-f7fb9784d9f7"
          passwordPolicy_id="ce93fcda-dc3a-4369-a7b0-a6108c261c85"/>
@@ -957,7 +959,7 @@ $$ }&#10;
             logout="0" csrf="1" routeType="PROTECTED"
             
predicates="[{&quot;cond&quot;:null,&quot;factory&quot;:&quot;METHOD&quot;,&quot;args&quot;:&quot;GET&quot;}]"/>
 
-  <SyncopeRole id="GROUP_OWNER" 
entitlements='["USER_SEARCH","USER_READ","USER_CREATE","USER_UPDATE","USER_DELETE","ANYTYPECLASS_READ","ANYTYPE_LIST","ANYTYPECLASS_LIST","RELATIONSHIPTYPE_LIST","ANYTYPE_READ","REALM_LIST","GROUP_SEARCH","GROUP_READ","GROUP_UPDATE","GROUP_DELETE"]'/>
+  <SyncopeRole id="GROUP_OWNER" 
entitlements='["USER_SEARCH","USER_READ","USER_CREATE","USER_UPDATE","USER_DELETE","ANYTYPECLASS_READ","ANYTYPE_LIST","ANYTYPECLASS_LIST","RELATIONSHIPTYPE_LIST","ANYTYPE_READ","REALM_SEARCH","GROUP_SEARCH","GROUP_READ","GROUP_UPDATE","GROUP_DELETE"]'/>
 
   <AuditConf 
id="syncope.audit.[LOGIC]:[SyncopeLogic]:[]:[isSelfRegAllowed]:[SUCCESS]" 
active="1"/>
 
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/XMLContentExporter.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/XMLContentExporter.java
index 5786905f32..e8c1af1d12 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/XMLContentExporter.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/XMLContentExporter.java
@@ -71,6 +71,7 @@ import org.apache.commons.lang3.tuple.Pair;
 import org.apache.cxf.helpers.IOUtils;
 import org.apache.openjpa.lib.util.collections.BidiMap;
 import org.apache.openjpa.lib.util.collections.DualHashBidiMap;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.core.persistence.api.DomainHolder;
 import org.apache.syncope.core.persistence.api.content.ContentExporter;
 import org.apache.syncope.core.persistence.api.dao.AuditConfDAO;
@@ -400,11 +401,10 @@ public class XMLContentExporter implements 
ContentExporter {
             if (tableName.equalsIgnoreCase(JPARealm.TABLE)) {
                 List<Map<String, String>> realmRows = new ArrayList<>(rows);
                 rows.clear();
-                realmDAO.findAll().forEach(realm -> 
realmRows.stream().filter(row -> {
-                    String id = row.get("ID");
-                    if (id == null) {
-                        id = row.get("id");
-                    }
+                realmDAO.findDescendants(SyncopeConstants.ROOT_REALM, null, 
-1, -1).
+                        forEach(realm -> realmRows.stream().filter(row -> {
+
+                    String id = 
Optional.ofNullable(row.get("ID")).orElseGet(() -> row.get("id"));
                     return realm.getKey().equals(id);
                 }).findFirst().ifPresent(rows::add));
             }
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
index 1297e0aa15..34ba905097 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
@@ -22,6 +22,7 @@ import jakarta.persistence.Query;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.ArrayUtils;
@@ -130,16 +131,15 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO 
{
                     goRealm -> groupOwners.add(goRealm.getRight()),
                     () -> {
                         if (realmPath.startsWith("/")) {
-                            Realm realm = realmDAO.findByFullPath(realmPath);
-                            if (realm == null) {
-                                SyncopeClientException noRealm = 
SyncopeClientException.build(
-                                        ClientExceptionType.InvalidRealm);
+                            Realm realm = 
Optional.ofNullable(realmDAO.findByFullPath(realmPath)).orElseThrow(() -> {
+                                SyncopeClientException noRealm =
+                                        
SyncopeClientException.build(ClientExceptionType.InvalidRealm);
                                 noRealm.getElements().add("Invalid realm 
specified: " + realmPath);
-                                throw noRealm;
-                            } else {
-                                
realmKeys.addAll(realmDAO.findDescendants(realm).stream().
-                                        
map(Realm::getKey).collect(Collectors.toSet()));
-                            }
+                                return noRealm;
+                            });
+
+                            
realmKeys.addAll(realmDAO.findDescendants(realm.getFullPath(), null, -1, 
-1).stream().
+                                    
map(Realm::getKey).collect(Collectors.toSet()));
                         } else {
                             DynRealm dynRealm = dynRealmDAO.find(realmPath);
                             if (dynRealm == null) {
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
index b21162c908..1a775cded8 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.persistence.jpa.dao;
 
 import jakarta.persistence.NoResultException;
+import jakarta.persistence.Query;
 import jakarta.persistence.TypedQuery;
 import java.util.ArrayList;
 import java.util.List;
@@ -52,8 +53,8 @@ public class JPARealmDAO extends AbstractDAO<Realm> 
implements RealmDAO {
 
     @Override
     public Realm getRoot() {
-        TypedQuery<Realm> query = entityManager().createQuery(
-                "SELECT e FROM " + JPARealm.class.getSimpleName() + " e WHERE 
e.parent IS NULL", Realm.class);
+        TypedQuery<Realm> query = entityManager().createQuery("SELECT e FROM " 
+ JPARealm.class.getSimpleName() + " e "
+                + "WHERE e.parent IS NULL", Realm.class);
 
         Realm result = null;
         try {
@@ -82,35 +83,16 @@ public class JPARealmDAO extends AbstractDAO<Realm> 
implements RealmDAO {
             throw new MalformedPathException(fullPath);
         }
 
-        Realm root = getRoot();
-        if (root == null) {
-            return null;
-        }
+        TypedQuery<Realm> query = entityManager().createQuery("SELECT e FROM " 
+ JPARealm.class.getSimpleName() + " e "
+                + "WHERE e.fullPath=:fullPath", Realm.class);
+        query.setParameter("fullPath", fullPath);
 
-        Realm current = root;
-        for (String pathElement : fullPath.substring(1).split("/")) {
-            current = findChildren(current).stream().
-                    filter(realm -> 
pathElement.equals(realm.getName())).findFirst().
-                    orElse(null);
-            if (current == null) {
-                return null;
-            }
+        Realm result = null;
+        try {
+            result = query.getSingleResult();
+        } catch (NoResultException e) {
+            LOG.debug("Root realm not found", e);
         }
-        return current;
-    }
-
-    private <T extends Policy> List<Realm> findSamePolicyChildren(final Realm 
realm, final T policy) {
-        List<Realm> result = new ArrayList<>();
-
-        findChildren(realm).stream().
-                filter(child -> (policy instanceof AccountPolicy
-                && child.getAccountPolicy() == null || 
policy.equals(child.getAccountPolicy()))
-                || (policy instanceof PasswordPolicy
-                && child.getPasswordPolicy() == null || 
policy.equals(child.getPasswordPolicy()))).
-                forEach(child -> {
-                    result.add(child);
-                    result.addAll(findSamePolicyChildren(child, policy));
-                });
 
         return result;
     }
@@ -133,15 +115,90 @@ public class JPARealmDAO extends AbstractDAO<Realm> 
implements RealmDAO {
         return query.getResultList();
     }
 
+    protected int setParameter(final List<Object> parameters, final Object 
parameter) {
+        parameters.add(parameter);
+        return parameters.size();
+    }
+
+    protected StringBuilder buildDescendantQuery(
+            final String base,
+            final String keyword,
+            final List<Object> parameters) {
+
+        StringBuilder queryString = new StringBuilder("SELECT e FROM ").
+                append(JPARealm.class.getSimpleName()).append(" e ").
+                append("WHERE (e.fullPath=?").
+                append(setParameter(parameters, base)).
+                append(" OR e.fullPath LIKE ?").
+                append(setParameter(parameters, 
SyncopeConstants.ROOT_REALM.equals(base) ? "/%" : base + "/%")).
+                append(')');
+
+        if (keyword != null) {
+            queryString.append(" AND LOWER(e.name) LIKE 
?").append(setParameter(parameters, keyword));
+        }
+
+        return queryString;
+    }
+
     @Override
-    public List<Realm> findMatching(final String keyword) {
-        TypedQuery<Realm> query = entityManager().createQuery("SELECT e FROM " 
+ JPARealm.class.getSimpleName() + " e "
-                + "WHERE e.name LIKE :keyword", Realm.class);
-        query.setParameter("keyword", keyword);
+    public int countDescendants(final String base, final String keyword) {
+        List<Object> parameters = new ArrayList<>();
+
+        StringBuilder queryString = buildDescendantQuery(base, keyword, 
parameters);
+        Query query = entityManager().createQuery(StringUtils.replaceOnce(
+                queryString.toString(),
+                "SELECT e ",
+                "SELECT COUNT(e) "));
+
+        for (int i = 1; i <= parameters.size(); i++) {
+            query.setParameter(i, parameters.get(i - 1));
+        }
+
+        return ((Number) query.getSingleResult()).intValue();
+    }
+
+    @Override
+    public List<Realm> findDescendants(
+            final String base,
+            final String keyword,
+            final int page,
+            final int itemsPerPage) {
+
+        List<Object> parameters = new ArrayList<>();
+
+        StringBuilder queryString = buildDescendantQuery(base, keyword, 
parameters);
+        TypedQuery<Realm> query = entityManager().createQuery(
+                queryString.append(" ORDER BY e.fullPath").toString(), 
Realm.class);
+
+        for (int i = 1; i <= parameters.size(); i++) {
+            query.setParameter(i, parameters.get(i - 1));
+        }
+
+        query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));
+
+        if (itemsPerPage > 0) {
+            query.setMaxResults(itemsPerPage);
+        }
 
         return query.getResultList();
     }
 
+    protected <T extends Policy> List<Realm> findSamePolicyChildren(final 
Realm realm, final T policy) {
+        List<Realm> result = new ArrayList<>();
+
+        findChildren(realm).stream().
+                filter(child -> (policy instanceof AccountPolicy
+                && child.getAccountPolicy() == null || 
policy.equals(child.getAccountPolicy()))
+                || (policy instanceof PasswordPolicy
+                && child.getPasswordPolicy() == null || 
policy.equals(child.getPasswordPolicy()))).
+                forEach(child -> {
+                    result.add(child);
+                    result.addAll(findSamePolicyChildren(child, policy));
+                });
+
+        return result;
+    }
+
     @Override
     public <T extends Policy> List<Realm> findByPolicy(final T policy) {
         if (policy instanceof PropagationPolicy || policy instanceof 
ProvisioningPolicy) {
@@ -187,7 +244,7 @@ public class JPARealmDAO extends AbstractDAO<Realm> 
implements RealmDAO {
         return query.getResultList();
     }
 
-    private static void findAncestors(final List<Realm> result, final Realm 
realm) {
+    protected void findAncestors(final List<Realm> result, final Realm realm) {
         if (realm.getParent() != null && !result.contains(realm.getParent())) {
             result.add(realm.getParent());
             findAncestors(result, realm.getParent());
@@ -211,35 +268,35 @@ public class JPARealmDAO extends AbstractDAO<Realm> 
implements RealmDAO {
         return query.getResultList();
     }
 
-    private void findDescendants(final List<Realm> result, final Realm realm) {
-        result.add(realm);
-        List<Realm> children = findChildren(realm);
-        if (children != null) {
-            children.forEach(child -> findDescendants(result, child));
-        }
-    }
-
-    @Override
-    public List<Realm> findDescendants(final Realm realm) {
-        List<Realm> result = new ArrayList<>();
-        findDescendants(result, realm);
-        return result;
+    protected StringBuilder buildDescendantQuery(final String base, final 
List<Object> parameters) {
+        return new StringBuilder("SELECT e FROM ").
+                append(JPARealm.class.getSimpleName()).append(" e ").
+                append("WHERE e.fullPath=?").
+                append(setParameter(parameters, base)).
+                append(" OR e.fullPath LIKE ?").
+                append(setParameter(parameters, 
SyncopeConstants.ROOT_REALM.equals(base) ? "/%" : base + "/%")).
+                append(" ORDER BY e.fullPath");
     }
 
-    @Transactional(readOnly = true)
-    @Override
-    public List<Realm> findAll() {
-        return findDescendants(getRoot());
+    protected String buildFullPath(final Realm realm) {
+        return realm.getParent() == null
+                ? SyncopeConstants.ROOT_REALM
+                : StringUtils.appendIfMissing(realm.getParent().getFullPath(), 
"/") + realm.getName();
     }
 
     @Override
     public Realm save(final Realm realm) {
+        ((JPARealm) realm).setFullPath(buildFullPath(realm));
         return entityManager().merge(realm);
     }
 
     @Override
     public void delete(final Realm realm) {
-        findDescendants(realm).forEach(toBeDeleted -> {
+        if (realm == null || realm.getParent() == null) {
+            return;
+        }
+
+        findDescendants(realm.getFullPath(), null, -1, -1).forEach(toBeDeleted 
-> {
             roleDAO.findByRealm(toBeDeleted).forEach(role -> 
role.getRealms().remove(toBeDeleted));
 
             toBeDeleted.setParent(null);
@@ -247,14 +304,4 @@ public class JPARealmDAO extends AbstractDAO<Realm> 
implements RealmDAO {
             entityManager().remove(toBeDeleted);
         });
     }
-
-    @Override
-    public void delete(final String key) {
-        Realm realm = find(key);
-        if (realm == null) {
-            return;
-        }
-
-        delete(realm);
-    }
 }
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
index d6c0d74035..16f61be329 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
@@ -303,7 +303,7 @@ public class JPATaskDAO extends AbstractDAO<Task<?>> 
implements TaskDAO {
             String realmKeysArg = 
AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.TASK_LIST).stream().
                     map(realmDAO::findByFullPath).
                     filter(Objects::nonNull).
-                    flatMap(r -> realmDAO.findDescendants(r).stream()).
+                    flatMap(r -> realmDAO.findDescendants(r.getFullPath(), 
null, -1, -1).stream()).
                     map(Realm::getKey).
                     distinct().
                     map(realmKey -> "?" + setParameter(parameters, realmKey)).
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARealm.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARealm.java
index 528bad8331..a3ac8a13ef 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARealm.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARealm.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.persistence.jpa.entity;
 
 import jakarta.persistence.Cacheable;
 import jakarta.persistence.CascadeType;
+import jakarta.persistence.Column;
 import jakarta.persistence.Entity;
 import jakarta.persistence.FetchType;
 import jakarta.persistence.JoinColumn;
@@ -34,8 +35,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.syncope.core.persistence.api.entity.AnyTemplateRealm;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
@@ -73,6 +72,9 @@ public class JPARealm extends AbstractGeneratedKeyEntity 
implements Realm {
     @ManyToOne
     private JPARealm parent;
 
+    @Column(nullable = false, unique = true)
+    private String fullPath;
+
     @ManyToOne(fetch = FetchType.EAGER)
     private JPAPasswordPolicy passwordPolicy;
 
@@ -125,9 +127,7 @@ public class JPARealm extends AbstractGeneratedKeyEntity 
implements Realm {
 
     @Override
     public String getFullPath() {
-        return getParent() == null
-                ? SyncopeConstants.ROOT_REALM
-                : StringUtils.appendIfMissing(getParent().getFullPath(), "/") 
+ getName();
+        return fullPath;
     }
 
     @Override
@@ -151,6 +151,10 @@ public class JPARealm extends AbstractGeneratedKeyEntity 
implements Realm {
         this.parent = (JPARealm) parent;
     }
 
+    public void setFullPath(final String fullPath) {
+        this.fullPath = fullPath;
+    }
+
     @Override
     public void setAccountPolicy(final AccountPolicy accountPolicy) {
         checkType(accountPolicy, JPAAccountPolicy.class);
diff --git a/core/persistence-jpa/src/main/resources/domains/MasterContent.xml 
b/core/persistence-jpa/src/main/resources/domains/MasterContent.xml
index c0c0981a3a..68ef918311 100644
--- a/core/persistence-jpa/src/main/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/main/resources/domains/MasterContent.xml
@@ -18,7 +18,7 @@ specific language governing permissions and limitations
 under the License.
 -->
 <dataset>
-  <Realm id="ea696a4f-e77a-4ef1-be67-8f8093bc8686" name="/"/>
+  <Realm id="ea696a4f-e77a-4ef1-be67-8f8093bc8686" name="/" fullPath="/"/>
 
   <AnyType id="USER" kind="USER"/>
   <AnyTypeClass id="BaseUser"/>
@@ -104,5 +104,5 @@ we are happy to inform you that the password request was 
successfully executed f
                 sender="[email protected]" subject="Password Reset 
successful" template_id="confirmPasswordReset" 
                 traceLevel="FAILURES" 
events='["[CUSTOM]:[]:[]:[confirmPasswordReset]:[SUCCESS]"]'/> 
 
-  <SyncopeRole id="GROUP_OWNER" 
entitlements='["USER_SEARCH","USER_READ","USER_CREATE","USER_UPDATE","USER_DELETE","ANYTYPECLASS_READ","ANYTYPE_LIST","ANYTYPECLASS_LIST","RELATIONSHIPTYPE_LIST","ANYTYPE_READ","REALM_LIST","GROUP_SEARCH","GROUP_READ","GROUP_UPDATE","GROUP_DELETE"]'/>
+  <SyncopeRole id="GROUP_OWNER" 
entitlements='["USER_SEARCH","USER_READ","USER_CREATE","USER_UPDATE","USER_DELETE","ANYTYPECLASS_READ","ANYTYPE_LIST","ANYTYPECLASS_LIST","RELATIONSHIPTYPE_LIST","ANYTYPE_READ","REALM_SEARCH","GROUP_SEARCH","GROUP_READ","GROUP_UPDATE","GROUP_DELETE"]'/>
 </dataset>
diff --git a/core/persistence-jpa/src/main/resources/indexes.xml 
b/core/persistence-jpa/src/main/resources/indexes.xml
index d15d1c2815..7626e7b58e 100644
--- a/core/persistence-jpa/src/main/resources/indexes.xml
+++ b/core/persistence-jpa/src/main/resources/indexes.xml
@@ -21,6 +21,9 @@ under the License.
 <properties>
   <comment>Additional indexes (in respect to JPA's)</comment>
 
+  <entry key="Realm_parent_id">CREATE INDEX Realm_parent_id ON 
Realm(parent_id)</entry>
+  <entry key="Realm_fullPath">CREATE INDEX Realm_fullPath ON 
Realm(fullPath)</entry>
+
   <entry key="SyncopeUser_realm_id">CREATE INDEX SyncopeUser_realm_id ON 
SyncopeUser(realm_id)</entry>
   <entry key="SyncopeUser_username">CREATE UNIQUE INDEX SyncopeUser_username 
ON SyncopeUser(username)</entry>
 
diff --git a/core/persistence-jpa/src/main/resources/oracle_indexes.xml 
b/core/persistence-jpa/src/main/resources/oracle_indexes.xml
index 6997204d8c..3d864abf7f 100644
--- a/core/persistence-jpa/src/main/resources/oracle_indexes.xml
+++ b/core/persistence-jpa/src/main/resources/oracle_indexes.xml
@@ -21,6 +21,9 @@ under the License.
 <properties>
   <comment>Additional indexes (in respect to JPA's)</comment>
 
+  <entry key="Realm_parent_id">CREATE INDEX Realm_parent_id ON 
Realm(parent_id)</entry>
+  <entry key="Realm_fullPath">CREATE INDEX Realm_fullPath ON 
Realm(fullPath)</entry>
+  
   <entry key="SyncopeUser_realm_id">CREATE INDEX SyncopeUser_realm_id ON 
SyncopeUser(realm_id)</entry>
   <entry key="SyncopeUser_lower_username">CREATE INDEX 
SyncopeUser_lower_username ON SyncopeUser(LOWER(username))</entry>
 
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java
index 02138dd4a6..0d62ccf264 100644
--- 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java
@@ -83,8 +83,10 @@ public class MultitenancyTest extends AbstractTest {
 
     @Test
     public void readRealm() {
-        assertEquals(1, realmDAO.findAll().size());
-        assertEquals(realmDAO.getRoot(), realmDAO.findAll().get(0));
+        assertEquals(1, 
realmDAO.findDescendants(realmDAO.getRoot().getFullPath(), null, -1, 
-1).size());
+        assertEquals(
+                realmDAO.getRoot(),
+                realmDAO.findDescendants(realmDAO.getRoot().getFullPath(), 
null, -1, -1).get(0));
     }
 
     @Test
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/RealmTest.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/RealmTest.java
index 1ae07640d2..2115cf9250 100644
--- 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/RealmTest.java
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/RealmTest.java
@@ -95,7 +95,7 @@ public class RealmTest extends AbstractTest {
 
     @Test
     public void findAll() {
-        List<Realm> list = realmDAO.findAll();
+        List<Realm> list = 
realmDAO.findDescendants(realmDAO.getRoot().getFullPath(), null, -1, -1);
         assertNotNull(list);
         assertFalse(list.isEmpty());
         list.forEach(Assertions::assertNotNull);
@@ -161,10 +161,10 @@ public class RealmTest extends AbstractTest {
         Realm actual = realmDAO.save(realm);
         assertNotNull(actual);
 
-        String id = actual.getKey();
-        assertNotNull(realmDAO.find(id));
+        actual = realmDAO.find(actual.getKey());
+        assertNotNull(actual);
 
-        realmDAO.delete(id);
-        assertNull(realmDAO.find(id));
+        realmDAO.delete(actual);
+        assertNull(realmDAO.find(actual.getKey()));
     }
 }
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/XMLContentExporterTest.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/XMLContentExporterTest.java
index 2bb59eccb8..42919ba697 100644
--- 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/XMLContentExporterTest.java
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/XMLContentExporterTest.java
@@ -60,8 +60,8 @@ public class XMLContentExporterTest extends AbstractTest {
                 filter(row -> 
row.trim().startsWith("<Realm")).collect(Collectors.toList());
         assertEquals(4, realms.size());
         assertTrue(realms.get(0).contains("name=\"/\""));
-        assertTrue(realms.get(1).contains("name=\"odd\""));
-        assertTrue(realms.get(2).contains("name=\"even\""));
-        assertTrue(realms.get(3).contains("name=\"two\""));
+        assertTrue(realms.get(1).contains("name=\"even\""));
+        assertTrue(realms.get(2).contains("name=\"two\""));
+        assertTrue(realms.get(3).contains("name=\"odd\""));
     }
 }
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml 
b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index 61ad9a8925..e153317e47 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -122,12 +122,14 @@ under the License.
       
   <AnyTypeClass id="csv"/>
 
-  <Realm id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28" name="/" 
passwordPolicy_id="986d1236-3ac5-4a19-810c-5ab21d79cba1"/>
-  <Realm id="722f3d84-9c2b-4525-8f6e-e4b82c55a36c" name="odd" 
-         parent_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28" 
accountPolicy_id="06e2ed52-6966-44aa-a177-a0ca7434201f"/>
-  <Realm id="c5b75db1-fce7-470f-b780-3b9934d82a9d" name="even" 
+  <Realm id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28" name="/"  fullPath="/"
+         passwordPolicy_id="986d1236-3ac5-4a19-810c-5ab21d79cba1"/>
+  <Realm id="722f3d84-9c2b-4525-8f6e-e4b82c55a36c" name="odd" fullPath="/odd" 
+         parent_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28"
+         accountPolicy_id="06e2ed52-6966-44aa-a177-a0ca7434201f"/>
+  <Realm id="c5b75db1-fce7-470f-b780-3b9934d82a9d" name="even" 
fullPath="/even" 
          parent_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28"/>
-  <Realm id="0679e069-7355-4b20-bd11-a5a0a5453c7c" name="two" 
+  <Realm id="0679e069-7355-4b20-bd11-a5a0a5453c7c" name="two" 
fullPath="/even/two"
          parent_id="c5b75db1-fce7-470f-b780-3b9934d82a9d"
          accountPolicy_id="20ab5a8c-4b0c-432c-b957-f7fb9784d9f7"
          passwordPolicy_id="ce93fcda-dc3a-4369-a7b0-a6108c261c85"/>
@@ -1043,7 +1045,7 @@ $$ }&#10;
             logout="0" csrf="1" routeType="PROTECTED"
             
predicates="[{&quot;cond&quot;:null,&quot;factory&quot;:&quot;METHOD&quot;,&quot;args&quot;:&quot;GET&quot;}]"/>
 
-  <SyncopeRole id="GROUP_OWNER" 
entitlements='["USER_SEARCH","USER_READ","USER_CREATE","USER_UPDATE","USER_DELETE","ANYTYPECLASS_READ","ANYTYPE_LIST","ANYTYPECLASS_LIST","RELATIONSHIPTYPE_LIST","ANYTYPE_READ","REALM_LIST","GROUP_SEARCH","GROUP_READ","GROUP_UPDATE","GROUP_DELETE"]'/>
+  <SyncopeRole id="GROUP_OWNER" 
entitlements='["USER_SEARCH","USER_READ","USER_CREATE","USER_UPDATE","USER_DELETE","ANYTYPECLASS_READ","ANYTYPE_LIST","ANYTYPECLASS_LIST","RELATIONSHIPTYPE_LIST","ANYTYPE_READ","REALM_SEARCH","GROUP_SEARCH","GROUP_READ","GROUP_UPDATE","GROUP_DELETE"]'/>
 
   <AuditConf 
id="syncope.audit.[LOGIC]:[SyncopeLogic]:[]:[isSelfRegAllowed]:[SUCCESS]" 
active="1"/>
 
diff --git a/core/persistence-jpa/src/test/resources/domains/TwoContent.xml 
b/core/persistence-jpa/src/test/resources/domains/TwoContent.xml
index c0b90e5d37..43c75da1dd 100644
--- a/core/persistence-jpa/src/test/resources/domains/TwoContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/TwoContent.xml
@@ -18,7 +18,7 @@ specific language governing permissions and limitations
 under the License.
 -->
 <dataset>
-  <Realm id="ea696a4f-e77a-4ef1-be67-8f8093bc8686" name="/"/>
+  <Realm id="ea696a4f-e77a-4ef1-be67-8f8093bc8686" name="/" fullPath="/"/>
 
   <AnyType id="USER" kind="USER"/>
   <AnyTypeClass id="BaseUser"/>
@@ -100,5 +100,5 @@ we are happy to inform you that the password request was 
successfully executed f
                 
jsonConf='[{"schema":{"name":"synchronizePasswords","displayName":"Enable 
Password Synchronization","helpMessage":"If true, the connector will 
synchronize passwords. The Password Capture Plugin needs to be installed for 
password synchronization to 
work.","type":"boolean","required":false,"order":0,"confidential":false,"defaultValues":null},"overridable":false,"values":["false"]},{"schema":{"name":"maintainLdapGroupMembership","displayName":"Maintain
 LDAP Group Membership" [...]
                 capabilities='["CREATE","UPDATE","DELETE","SEARCH"]'/>
 
-  <SyncopeRole id="GROUP_OWNER" 
entitlements='["USER_SEARCH","USER_READ","USER_CREATE","USER_UPDATE","USER_DELETE","ANYTYPECLASS_READ","ANYTYPE_LIST","ANYTYPECLASS_LIST","RELATIONSHIPTYPE_LIST","ANYTYPE_READ","REALM_LIST","GROUP_SEARCH","GROUP_READ","GROUP_UPDATE","GROUP_DELETE"]'/>
+  <SyncopeRole id="GROUP_OWNER" 
entitlements='["USER_SEARCH","USER_READ","USER_CREATE","USER_UPDATE","USER_DELETE","ANYTYPECLASS_READ","ANYTYPE_LIST","ANYTYPECLASS_LIST","RELATIONSHIPTYPE_LIST","ANYTYPE_READ","REALM_SEARCH","GROUP_SEARCH","GROUP_READ","GROUP_UPDATE","GROUP_DELETE"]'/>
 </dataset>
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java
index f73649b02e..798e6bfb95 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java
@@ -27,6 +27,7 @@ import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.Item;
 import org.apache.syncope.common.lib.to.OrgUnit;
 import org.apache.syncope.common.lib.to.Provision;
@@ -488,9 +489,7 @@ public class InboundMatcher {
 
             case "name":
                 if (orgUnit.isIgnoreCaseMatch()) {
-                    String realmName = connObjectKey;
-                    result.addAll(realmDAO.findAll().stream().
-                            filter(r -> 
r.getName().equalsIgnoreCase(realmName)).collect(Collectors.toList()));
+                    
result.addAll(realmDAO.findDescendants(SyncopeConstants.ROOT_REALM, 
connObjectKey, -1, -1));
                 } else {
                     
result.addAll(realmDAO.findByName(connObjectKey).stream().collect(Collectors.toList()));
                 }
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
index f2310fabef..f72667d1ed 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
@@ -206,7 +206,8 @@ public class PushJobDelegate extends 
AbstractProvisioningJobDelegate<PushTask> i
             });
 
             // Never push the root realm
-            List<Realm> realms = 
realmDAO.findDescendants(profile.getTask().getSourceRealm()).stream().
+            List<Realm> realms = realmDAO.findDescendants(
+                    profile.getTask().getSourceRealm().getFullPath(), null, 
-1, -1).stream().
                     filter(realm -> realm.getParent() != 
null).collect(Collectors.toList());
             boolean result = true;
             for (int i = 0; i < realms.size() && result; i++) {
diff --git 
a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchClientContext.java
 
b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchClientContext.java
index 4f9b3d2af2..499922b80b 100644
--- 
a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchClientContext.java
+++ 
b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchClientContext.java
@@ -72,7 +72,7 @@ public class ElasticsearchClientContext {
         return new ElasticsearchIndexLoader(indexManager);
     }
 
-    @ConditionalOnMissingBean
+    @ConditionalOnMissingBean(name = "syncopeElasticsearchHealthContributor")
     @Bean(name = {
         "syncopeElasticsearchHealthContributor", 
"elasticsearchHealthIndicator", "elasticsearchHealthContributor" })
     public HealthContributor syncopeElasticsearchHealthContributor(final 
ElasticsearchClient client) {
diff --git 
a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
 
b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
index 726b8b8047..c1ba3cd8b7 100644
--- 
a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
+++ 
b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
@@ -158,7 +158,7 @@ public class ElasticsearchAnySearchDAO extends 
AbstractAnySearchDAO {
                         return noRealm;
                     });
 
-                    realmDAO.findDescendants(realm).forEach(descendant -> 
queries.add(
+                    realmDAO.findDescendants(realm.getFullPath(), null, -1, 
-1).forEach(descendant -> queries.add(
                             new Query.Builder().term(QueryBuilders.term().
                                     
field("realm").value(FieldValue.of(descendant.getKey())).build()).
                                     build()));
diff --git 
a/ext/elasticsearch/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAOTest.java
 
b/ext/elasticsearch/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAOTest.java
index 1adbd0001b..f17e8c5e48 100644
--- 
a/ext/elasticsearch/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAOTest.java
+++ 
b/ext/elasticsearch/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAOTest.java
@@ -114,9 +114,10 @@ public class ElasticsearchAnySearchDAOTest {
         // 1. mock
         Realm root = mock(Realm.class);
         when(root.getKey()).thenReturn("rootKey");
+        when(root.getFullPath()).thenReturn(SyncopeConstants.ROOT_REALM);
 
         
when(realmDAO.findByFullPath(SyncopeConstants.ROOT_REALM)).thenReturn(root);
-        when(realmDAO.findDescendants(root)).thenReturn(List.of(root));
+        when(realmDAO.findDescendants(SyncopeConstants.ROOT_REALM, null, -1, 
-1)).thenReturn(List.of(root));
 
         // 2. test
         Set<String> adminRealms = Set.of(SyncopeConstants.ROOT_REALM);
diff --git 
a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestCommand.java
 
b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestCommand.java
index 8ad73db82f..043b77b20a 100644
--- 
a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestCommand.java
+++ 
b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestCommand.java
@@ -43,7 +43,8 @@ public class TestCommand implements Command<TestCommandArgs> {
     private AnyObjectLogic anyObjectLogic;
 
     private Optional<RealmTO> getRealm(final String fullPath) {
-        return realmLogic.list(fullPath).stream().filter(realm -> 
fullPath.equals(realm.getFullPath())).findFirst();
+        return realmLogic.search(null, fullPath, -1, -1).getRight().stream().
+                filter(realm -> 
fullPath.equals(realm.getFullPath())).findFirst();
     }
 
     @Transactional(propagation = Propagation.NOT_SUPPORTED)
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
index cd4558f909..622bf6da8b 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
@@ -112,6 +112,7 @@ import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.batch.BatchPayloadParser;
 import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
 import org.apache.syncope.common.rest.api.beans.AuditQuery;
+import org.apache.syncope.common.rest.api.beans.RealmQuery;
 import org.apache.syncope.common.rest.api.service.AnyObjectService;
 import org.apache.syncope.common.rest.api.service.AnyTypeClassService;
 import org.apache.syncope.common.rest.api.service.AnyTypeService;
@@ -1003,7 +1004,8 @@ public abstract class AbstractITCase {
     }
 
     protected static Optional<RealmTO> getRealm(final String fullPath) {
-        return REALM_SERVICE.list(fullPath).stream().filter(realm -> 
fullPath.equals(realm.getFullPath())).findFirst();
+        return REALM_SERVICE.search(new 
RealmQuery.Builder().base(fullPath).build()).getResult().stream().
+                filter(realm -> 
fullPath.equals(realm.getFullPath())).findFirst();
     }
 
     @Autowired
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/CommandITCase.java
 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/CommandITCase.java
index a34bb8255a..9b9c755a02 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/CommandITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/CommandITCase.java
@@ -36,6 +36,7 @@ import 
org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.syncope.common.lib.types.ImplementationEngine;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.beans.CommandQuery;
+import org.apache.syncope.common.rest.api.beans.RealmQuery;
 import org.apache.syncope.fit.AbstractITCase;
 import org.apache.syncope.fit.core.reference.TestCommand;
 import org.apache.syncope.fit.core.reference.TestCommandArgs;
@@ -107,7 +108,8 @@ public class CommandITCase extends AbstractITCase {
             AnyObjectTO printer = 
ANY_OBJECT_SERVICE.read(args.getPrinterName());
             assertNotNull(printer);
             assertEquals(args.getParentRealm() + "/" + args.getRealmName(), 
printer.getRealm());
-            assertFalse(REALM_SERVICE.list(printer.getRealm()).isEmpty());
+            assertFalse(REALM_SERVICE.search(
+                    new 
RealmQuery.Builder().base(printer.getRealm()).build()).getResult().isEmpty());
         } finally {
             ANY_OBJECT_SERVICE.delete(args.getPrinterName());
             REALM_SERVICE.delete(args.getParentRealm() + "/" + 
args.getRealmName());
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MacroITCase.java 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MacroITCase.java
index 1276a6ef24..c0452b98df 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MacroITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MacroITCase.java
@@ -45,6 +45,7 @@ import 
org.apache.syncope.common.lib.types.ImplementationEngine;
 import org.apache.syncope.common.lib.types.TaskType;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.beans.ExecSpecs;
+import org.apache.syncope.common.rest.api.beans.RealmQuery;
 import org.apache.syncope.common.rest.api.service.TaskService;
 import org.apache.syncope.fit.AbstractITCase;
 import org.apache.syncope.fit.core.reference.TestCommand;
@@ -134,7 +135,8 @@ public class MacroITCase extends AbstractITCase {
         AnyObjectTO printer = ANY_OBJECT_SERVICE.read(TCA.getPrinterName());
         assertNotNull(printer);
         assertEquals(TCA.getParentRealm() + "/" + TCA.getRealmName(), 
printer.getRealm());
-        assertFalse(REALM_SERVICE.list(printer.getRealm()).isEmpty());
+        assertFalse(REALM_SERVICE.search(
+                new 
RealmQuery.Builder().base(printer.getRealm()).build()).getResult().isEmpty());
     }
 
     @Test
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RealmITCase.java 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RealmITCase.java
index 681cfbd8f6..d6946059bc 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RealmITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RealmITCase.java
@@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
+import jakarta.ws.rs.NotFoundException;
 import jakarta.ws.rs.core.GenericType;
 import jakarta.ws.rs.core.Response;
 import java.util.List;
@@ -50,10 +51,8 @@ import 
org.apache.syncope.common.lib.types.ImplementationEngine;
 import org.apache.syncope.common.lib.types.PolicyType;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.beans.RealmQuery;
-import org.apache.syncope.common.rest.api.service.RealmService;
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 import org.apache.syncope.fit.AbstractITCase;
-import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 public class RealmITCase extends AbstractITCase {
@@ -64,21 +63,6 @@ public class RealmITCase extends AbstractITCase {
         assertTrue(match.getResult().stream().allMatch(realm -> 
realm.getName().contains("o")));
     }
 
-    @Test
-    public void list() {
-        List<RealmTO> realms = REALM_SERVICE.list(SyncopeConstants.ROOT_REALM);
-        assertNotNull(realms);
-        assertFalse(realms.isEmpty());
-        realms.forEach(Assertions::assertNotNull);
-
-        try {
-            REALM_SERVICE.list("a name");
-            fail("This should not happen");
-        } catch (SyncopeClientException e) {
-            assertEquals(ClientExceptionType.InvalidPath, e.getType());
-        }
-    }
-
     @Test
     public void createUpdate() {
         RealmTO realm = new RealmTO();
@@ -86,10 +70,8 @@ public class RealmITCase extends AbstractITCase {
 
         // 1. create
         Response response = REALM_SERVICE.create("/even/two", realm);
-        RealmTO[] actuals = getObject(response.getLocation(), 
RealmService.class, RealmTO[].class);
-        assertNotNull(actuals);
-        assertTrue(actuals.length > 0);
-        RealmTO actual = actuals[0];
+        RealmTO actual = 
getRealm(response.getHeaderString(RESTHeaders.RESOURCE_KEY)).
+                orElseThrow(() -> new NotFoundException());
         assertNotNull(actual.getKey());
         assertEquals("last", actual.getName());
         assertEquals("/even/two/last", actual.getFullPath());
@@ -120,8 +102,8 @@ public class RealmITCase extends AbstractITCase {
         assertNotNull(actual);
         assertEquals("/odd/last", actual.getFullPath());
 
-        assertEquals(1, 
REALM_SERVICE.list(SyncopeConstants.ROOT_REALM).stream().
-                filter(object -> 
realm.getName().equals(object.getName())).count());
+        assertEquals(1, REALM_SERVICE.search(new 
RealmQuery.Builder().base(SyncopeConstants.ROOT_REALM).build()).
+                getResult().stream().filter(r -> 
realm.getName().equals(r.getName())).count());
 
         // 4. create under invalid path
         try {
@@ -148,7 +130,8 @@ public class RealmITCase extends AbstractITCase {
         Response response = REALM_SERVICE.create("/even/two", realm);
         assertEquals(Response.Status.CREATED.getStatusCode(), 
response.getStatus());
 
-        List<RealmTO> realms = REALM_SERVICE.list("/even/two/73~1~19534");
+        List<RealmTO> realms = REALM_SERVICE.search(new RealmQuery.Builder().
+                base("/even/two/73~1~19534").build()).getResult();
         assertEquals(1, realms.size());
         assertEquals(realm.getName(), realms.get(0).getName());
     }
@@ -180,20 +163,16 @@ public class RealmITCase extends AbstractITCase {
         realm.setName("withPolicy");
 
         response = REALM_SERVICE.create(SyncopeConstants.ROOT_REALM, realm);
-        RealmTO[] actuals = getObject(response.getLocation(), 
RealmService.class, RealmTO[].class);
-        assertNotNull(actuals);
-        assertTrue(actuals.length > 0);
-        realm = actuals[0];
+        realm = getRealm(response.getHeaderString(RESTHeaders.RESOURCE_KEY)).
+                orElseThrow(() -> new NotFoundException());
 
         String existingAccountPolicy = realm.getAccountPolicy();
 
         realm.setAccountPolicy(policy.getKey());
         REALM_SERVICE.update(realm);
 
-        actuals = getObject(response.getLocation(), RealmService.class, 
RealmTO[].class);
-        assertNotNull(actuals);
-        assertTrue(actuals.length > 0);
-        RealmTO actual = actuals[0];
+        RealmTO actual = 
getRealm(response.getHeaderString(RESTHeaders.RESOURCE_KEY)).
+                orElseThrow(() -> new NotFoundException());
         assertEquals(policy.getKey(), actual.getAccountPolicy());
 
         // 3. remove policy
@@ -221,20 +200,16 @@ public class RealmITCase extends AbstractITCase {
         realm.setName("withAuthPolicy");
 
         Response response = REALM_SERVICE.create(SyncopeConstants.ROOT_REALM, 
realm);
-        RealmTO[] actuals = getObject(response.getLocation(), 
RealmService.class, RealmTO[].class);
-        assertNotNull(actuals);
-        assertTrue(actuals.length > 0);
-        realm = actuals[0];
+        realm = getRealm(response.getHeaderString(RESTHeaders.RESOURCE_KEY)).
+                orElseThrow(() -> new NotFoundException());
 
         String existingAuthPolicy = realm.getAuthPolicy();
 
         realm.setAuthPolicy(policy.getKey());
         REALM_SERVICE.update(realm);
 
-        actuals = getObject(response.getLocation(), RealmService.class, 
RealmTO[].class);
-        assertNotNull(actuals);
-        assertTrue(actuals.length > 0);
-        RealmTO actual = actuals[0];
+        RealmTO actual = 
getRealm(response.getHeaderString(RESTHeaders.RESOURCE_KEY)).
+                orElseThrow(() -> new NotFoundException());
         assertEquals(policy.getKey(), actual.getAuthPolicy());
 
         // 3. remove policy
@@ -262,20 +237,16 @@ public class RealmITCase extends AbstractITCase {
         realm.setName("withAccessPolicy");
 
         Response response = REALM_SERVICE.create(SyncopeConstants.ROOT_REALM, 
realm);
-        RealmTO[] actuals = getObject(response.getLocation(), 
RealmService.class, RealmTO[].class);
-        assertNotNull(actuals);
-        assertTrue(actuals.length > 0);
-        realm = actuals[0];
+        realm = getRealm(response.getHeaderString(RESTHeaders.RESOURCE_KEY)).
+                orElseThrow(() -> new NotFoundException());
 
         String existingAccessPolicy = realm.getAccessPolicy();
 
         realm.setAccessPolicy(policy.getKey());
         REALM_SERVICE.update(realm);
 
-        actuals = getObject(response.getLocation(), RealmService.class, 
RealmTO[].class);
-        assertNotNull(actuals);
-        assertTrue(actuals.length > 0);
-        RealmTO actual = actuals[0];
+        RealmTO actual = 
getRealm(response.getHeaderString(RESTHeaders.RESOURCE_KEY)).
+                orElseThrow(() -> new NotFoundException());
         assertEquals(policy.getKey(), actual.getAccessPolicy());
 
         // 3. remove policy
@@ -304,20 +275,16 @@ public class RealmITCase extends AbstractITCase {
         realm.setName("withAttrReleasePolicy");
 
         Response response = REALM_SERVICE.create(SyncopeConstants.ROOT_REALM, 
realm);
-        RealmTO[] actuals = getObject(response.getLocation(), 
RealmService.class, RealmTO[].class);
-        assertNotNull(actuals);
-        assertTrue(actuals.length > 0);
-        realm = actuals[0];
+        realm = getRealm(response.getHeaderString(RESTHeaders.RESOURCE_KEY)).
+                orElseThrow(() -> new NotFoundException());
 
         String existingAttrReleasePolicy = realm.getAttrReleasePolicy();
 
         realm.setAttrReleasePolicy(policy.getKey());
         REALM_SERVICE.update(realm);
 
-        actuals = getObject(response.getLocation(), RealmService.class, 
RealmTO[].class);
-        assertNotNull(actuals);
-        assertTrue(actuals.length > 0);
-        RealmTO actual = actuals[0];
+        RealmTO actual = 
getRealm(response.getHeaderString(RESTHeaders.RESOURCE_KEY)).
+                orElseThrow(() -> new NotFoundException());
         assertEquals(policy.getKey(), actual.getAttrReleasePolicy());
 
         // 3. remove policy
@@ -334,15 +301,13 @@ public class RealmITCase extends AbstractITCase {
         realm.setName("deletable3");
 
         Response response = REALM_SERVICE.create("/even/two", realm);
-        RealmTO[] actuals = getObject(response.getLocation(), 
RealmService.class, RealmTO[].class);
-        assertNotNull(actuals);
-        assertTrue(actuals.length > 0);
-        RealmTO actual = actuals[0];
+        RealmTO actual = 
getRealm(response.getHeaderString(RESTHeaders.RESOURCE_KEY)).
+                orElseThrow(() -> new NotFoundException());
 
         REALM_SERVICE.delete(actual.getFullPath());
 
         try {
-            REALM_SERVICE.list(actual.getFullPath());
+            REALM_SERVICE.search(new 
RealmQuery.Builder().base(actual.getFullPath()).build());
             fail("This should not happen");
         } catch (SyncopeClientException e) {
             assertEquals(ClientExceptionType.NotFound, e.getType());
@@ -421,7 +386,7 @@ public class RealmITCase extends AbstractITCase {
     @Test
     public void issueSYNCOPE1472() {
         // 1. assign twice resource-ldap-orgunit to /odd
-        RealmTO realmTO = REALM_SERVICE.list("/odd").get(0);
+        RealmTO realmTO = REALM_SERVICE.search(new 
RealmQuery.Builder().base("/odd").build()).getResult().get(0);
         realmTO.getResources().clear();
         realmTO.getResources().add("resource-ldap-orgunit");
         realmTO.getResources().add("resource-ldap-orgunit");
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java
 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java
index cd81a2050b..eff056471d 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java
@@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
+import jakarta.ws.rs.NotFoundException;
 import jakarta.ws.rs.core.Response;
 import java.util.ArrayList;
 import java.util.List;
@@ -55,10 +56,10 @@ import org.apache.syncope.common.lib.to.RoleTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
 import org.apache.syncope.common.rest.api.beans.ConnObjectTOQuery;
 import org.apache.syncope.common.rest.api.service.GroupService;
-import org.apache.syncope.common.rest.api.service.RealmService;
 import org.apache.syncope.common.rest.api.service.RoleService;
 import org.apache.syncope.fit.AbstractITCase;
 import org.identityconnectors.framework.common.objects.Name;
@@ -816,10 +817,8 @@ public class SearchITCase extends AbstractITCase {
 
         // 1. create Realm
         Response response = REALM_SERVICE.create("/even/two", realm);
-        RealmTO[] actuals = getObject(response.getLocation(), 
RealmService.class, RealmTO[].class);
-        assertNotNull(actuals);
-        assertTrue(actuals.length > 0);
-        realm = actuals[0];
+        realm = getRealm(response.getHeaderString(RESTHeaders.RESOURCE_KEY)).
+                orElseThrow(() -> new NotFoundException());
         assertNotNull(realm.getKey());
         assertEquals("syncope1727", realm.getName());
         assertEquals("/even/two/syncope1727", realm.getFullPath());
diff --git a/pom.xml b/pom.xml
index 3b2602abcf..3c6c48d054 100644
--- a/pom.xml
+++ b/pom.xml
@@ -431,7 +431,7 @@ under the License.
     <elasticsearch.version>8.7.0</elasticsearch.version>
 
     <disruptor.version>3.4.4</disruptor.version>
-    
+
     <commons-jexl.version>3.3</commons-jexl.version>
     <commons-text.version>1.10.0</commons-text.version>
 
@@ -500,9 +500,9 @@ under the License.
     <docker.mysql.version>8.0</docker.mysql.version>
     <docker.mariadb.version>10</docker.mariadb.version>
 
-    <jdbc.postgresql.version>42.5.4</jdbc.postgresql.version>
+    <jdbc.postgresql.version>42.6.0</jdbc.postgresql.version>
     <jdbc.mysql.version>8.0.32</jdbc.mysql.version>
-    <jdbc.mariadb.version>3.1.2</jdbc.mariadb.version>
+    <jdbc.mariadb.version>3.1.3</jdbc.mariadb.version>
     <jdbc.mssql.version>12.2.0.jre</jdbc.mssql.version>
     <jdbc.oracle.version>21.9.0.0</jdbc.oracle.version>
 
@@ -659,7 +659,7 @@ under the License.
       <dependency>
         <groupId>com.fasterxml.woodstox</groupId>
         <artifactId>woodstox-core</artifactId>
-        <version>6.4.0</version>
+        <version>6.5.0</version>
       </dependency>
 
       <dependency>
@@ -1706,7 +1706,7 @@ under the License.
         <plugin>
           <groupId>org.jacoco</groupId>
           <artifactId>jacoco-maven-plugin</artifactId>
-          <version>0.8.8</version>
+          <version>0.8.9</version>
         </plugin>
 
         <plugin>
@@ -1996,7 +1996,7 @@ under the License.
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-javadoc-plugin</artifactId>
-        <version>3.4.1</version>
+        <version>3.5.0</version>
         <configuration>
           <source>${targetJdk}</source>
           <destDir>apidocs/4.0</destDir>
@@ -2060,7 +2060,7 @@ under the License.
           <plugin>
             <groupId>org.cyclonedx</groupId>
             <artifactId>cyclonedx-maven-plugin</artifactId>
-            <version>2.7.6</version>
+            <version>2.7.7</version>
             <executions>
               <execution>
                 <phase>package</phase>
@@ -2133,7 +2133,7 @@ under the License.
           <plugin>
             <groupId>org.asciidoctor</groupId>
             <artifactId>asciidoctor-maven-plugin</artifactId>
-            <version>2.2.2</version>
+            <version>2.2.3</version>
             <inherited>false</inherited>
             <dependencies>
               <dependency>
diff --git a/src/main/asciidoc/reference-guide/concepts/entitlements.adoc 
b/src/main/asciidoc/reference-guide/concepts/entitlements.adoc
index f6cf1a4561..4986929cb9 100644
--- a/src/main/asciidoc/reference-guide/concepts/entitlements.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/entitlements.adoc
@@ -31,12 +31,12 @@ 
https://github.com/apache/syncope/blob/master/core/logic/src/main/java/org/apach
 endif::[]
 , the
 
https://docs.spring.io/spring-security/site/docs/5.5.x/reference/html5/#el-common-built-in[`hasRole`
 expression^]
-is used together with one of the standard entitlements to restrict access only 
to Users owning the `REALM_LIST`
+is used together with one of the standard entitlements to restrict access only 
to Users owning the `REALM_SEARCH`
 entitlement.
 
 [source,java]
 ----
-@PreAuthorize("hasRole('" + IdRepoEntitlement.REALM_LIST + "')")
+@PreAuthorize("hasRole('" + IdRepoEntitlement.REALM_SEARCH + "')")
 public List<RealmTO> list(final String fullPath) {
 ----
 
diff --git a/src/main/asciidoc/reference-guide/concepts/roles.adoc 
b/src/main/asciidoc/reference-guide/concepts/roles.adoc
index 216de6c2e1..83e8cfa313 100644
--- a/src/main/asciidoc/reference-guide/concepts/roles.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/roles.adoc
@@ -93,7 +93,7 @@ For example, the following entitlements are normally required 
to be granted for
 . `RELATIONSHIPTYPE_LIST`
 . `USER_READ`
 . `ANYTYPE_READ`
-. `REALM_LIST`
+. `REALM_SEARCH`
 . `GROUP_SEARCH`
 ====
 
@@ -122,7 +122,7 @@ The actual Entitlements are assigned through the predefined 
`GROUP_OWNER` Role:
 . `ANYTYPECLASS_LIST`
 . `RELATIONSHIPTYPE_LIST`
 . `ANYTYPE_READ`
-. `REALM_LIST`
+. `REALM_SEARCH`
 . `GROUP_SEARCH`
 . `GROUP_READ`
 . `GROUP_UPDATE`

Reply via email to