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

[syncope] branch master updated: [SYNCOPE-1696] Adding support to manage Audit entries via Elasticsearch (#387)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new f42ea0bb75 [SYNCOPE-1696] Adding support to manage Audit entries via Elasticsearch (#387)
f42ea0bb75 is described below

commit f42ea0bb756a82a39d42617a746f8f213b1f3ff8
Author: Francesco Chicchiriccò <il...@users.noreply.github.com>
AuthorDate: Thu Nov 3 13:05:46 2022 +0100

    [SYNCOPE-1696] Adding support to manage Audit entries via Elasticsearch (#387)
---
 .../syncope/common/lib/info/NumbersInfo.java       |  20 +-
 .../syncope/common/lib/info/PlatformInfo.java      | 243 ---------------------
 .../common/lib/types/ClientExceptionType.java      |   1 -
 .../core/logic/DummyImplementationLookup.java      |   5 -
 .../org/apache/syncope/core/logic/AuditLogic.java  |  26 ++-
 .../syncope/core/logic/IdRepoLogicContext.java     |  48 +++-
 .../apache/syncope/core/logic/LogicProperties.java |  35 ---
 .../org/apache/syncope/core/logic/RealmLogic.java  |   2 +-
 .../syncope/core/logic/audit/AuditAppender.java    |   2 -
 .../core/logic/audit/DefaultAuditAppender.java     |   8 +-
 .../logic/audit/DefaultRewriteAuditAppender.java   |   5 +-
 .../core/logic/audit/JdbcAuditAppender.java        |  52 ++---
 .../syncope/core/logic/init/AuditLoader.java       | 106 +++------
 .../init/ClassPathScanImplementationLookup.java    |  18 --
 .../core/logic/DummyImplementationLookup.java      |   5 -
 .../core/rest/cxf/IdRepoRESTCXFContext.java        |   3 +-
 .../core/persistence/api/ImplementationLookup.java |   2 -
 .../core/persistence/api/dao/AuditConfDAO.java     |   4 +-
 .../core/persistence/jpa/dao/JPAAuditConfDAO.java  |   8 +-
 .../persistence/jpa/DummyImplementationLookup.java |   5 -
 .../provisioning/api/serialization/POJOHelper.java |  12 +
 .../provisioning/java/DefaultAuditManager.java     |  15 +-
 .../AbstractPropagationTaskExecutor.java           |   3 +-
 .../pushpull/DefaultRealmPullResultHandler.java    |   2 +-
 .../java/DummyImplementationLookup.java            |   5 -
 .../spring/security/DummyImplementationLookup.java |   5 -
 .../core/starter/SyncopeCoreApplication.java       |  93 ++------
 .../actuate/DefaultSyncopeCoreInfoContributor.java | 236 ++++----------------
 core/starter/src/main/resources/core.properties    |   6 -
 .../client/ElasticsearchClientContext.java         |  26 ++-
 .../client/ElasticsearchIndexLoader.java}          |  34 +--
 .../client/ElasticsearchIndexManager.java          | 158 +++++++++++---
 .../client/ElasticsearchProperties.java            |  86 ++++++++
 .../elasticsearch/client/ElasticsearchUtils.java   |  74 +++----
 ext/elasticsearch/{ => logic}/pom.xml              |  44 ++--
 .../core/logic/audit/ElasticsearchAppender.java    |  97 ++++++++
 .../logic/audit/ElasticsearchAuditAppender.java    |  52 +++++
 .../logic/audit/ElasticsearchLogicContext.java     |  62 ++++++
 .../src/main/resources/META-INF/spring.factories   |   3 +-
 .../jpa/ElasticsearchPersistenceContext.java       |  26 ++-
 .../jpa/dao/ElasticsearchAnySearchDAO.java         |  12 +-
 .../jpa/dao/ElasticsearchAuditConfDAO.java         | 182 +++++++++++++++
 .../jpa/dao/ElasticsearchAnySearchDAOTest.java     |  26 ++-
 ext/elasticsearch/pom.xml                          |   1 +
 .../java/job/ElasticsearchReindex.java             |  31 ++-
 fit/core-reference/pom.xml                         |   6 +
 .../fit/core/reference/CoreReferenceContext.java   |  12 +
 .../fit/core/reference/ITImplementationLookup.java |  12 -
 .../core/reference/SyslogRewriteAuditAppender.java |  25 ++-
 .../fit/core/reference/TestFileAuditAppender.java  |  39 ++--
 .../reference/TestFileRewriteAuditAppender.java    |  23 +-
 .../main/resources/core-elasticsearch.properties   |   7 +-
 .../src/main/resources/core-embedded.properties    |   3 +
 .../org/apache/syncope/fit/AbstractITCase.java     |  27 +++
 .../org/apache/syncope/fit/AbstractUIITCase.java   |  25 +++
 .../apache/syncope/fit/ElasticsearchDetector.java  |  32 ---
 .../org/apache/syncope/fit/FlowableDetector.java   |  32 ---
 .../org/apache/syncope/fit/core/AuditITCase.java   |  77 ++++---
 .../syncope/fit/core/AuthenticationITCase.java     |   8 +-
 .../apache/syncope/fit/core/BpmnProcessITCase.java |   5 +-
 .../apache/syncope/fit/core/DelegationITCase.java  |   3 +-
 .../apache/syncope/fit/core/DynRealmITCase.java    |   7 +-
 .../org/apache/syncope/fit/core/GroupITCase.java   |   5 +-
 .../apache/syncope/fit/core/KeymasterITCase.java   |   3 +-
 .../apache/syncope/fit/core/MembershipITCase.java  |   3 +-
 .../syncope/fit/core/MultitenancyITCase.java       |   3 +-
 .../apache/syncope/fit/core/PullTaskITCase.java    |  10 +-
 .../org/apache/syncope/fit/core/RealmITCase.java   |  14 +-
 .../org/apache/syncope/fit/core/SearchITCase.java  |  13 +-
 .../org/apache/syncope/fit/core/UserITCase.java    |   7 +-
 .../apache/syncope/fit/core/UserIssuesITCase.java  |   3 +-
 .../apache/syncope/fit/core/UserRequestITCase.java |   5 +-
 .../apache/syncope/fit/core/UserSelfITCase.java    |  26 +--
 .../syncope/fit/enduser/AuthenticatedITCase.java   |   3 +-
 pom.xml                                            |  10 +-
 .../asciidoc/reference-guide/concepts/audit.adoc   |   5 +-
 .../reference-guide/concepts/extensions.adoc       |   6 +-
 .../reference-guide/usage/customization.adoc       |  27 ++-
 78 files changed, 1239 insertions(+), 1136 deletions(-)

diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/info/NumbersInfo.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/info/NumbersInfo.java
index a2a087410b..49fd9e03a0 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/info/NumbersInfo.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/info/NumbersInfo.java
@@ -57,7 +57,7 @@ public class NumbersInfo implements BaseBean {
         }
     }
 
-    public class TaskExecutorInfo {
+    public static class TaskExecutorInfo {
 
         private int size;
 
@@ -158,9 +158,7 @@ public class NumbersInfo implements BaseBean {
 
     private final Map<String, Boolean> confCompleteness = new HashMap<>();
 
-    private final TaskExecutorInfo asyncConnectorExecutor = new TaskExecutorInfo();
-
-    private final TaskExecutorInfo propagationTaskExecutor = new TaskExecutorInfo();
+    private final Map<String, TaskExecutorInfo> taskExecutorInfos = new HashMap<>();
 
     public int getTotalUsers() {
         return totalUsers;
@@ -250,12 +248,8 @@ public class NumbersInfo implements BaseBean {
         return confCompleteness;
     }
 
-    public TaskExecutorInfo getAsyncConnectorExecutor() {
-        return asyncConnectorExecutor;
-    }
-
-    public TaskExecutorInfo getPropagationTaskExecutor() {
-        return propagationTaskExecutor;
+    public Map<String, TaskExecutorInfo> getTaskExecutorInfos() {
+        return taskExecutorInfos;
     }
 
     @Override
@@ -275,8 +269,7 @@ public class NumbersInfo implements BaseBean {
                 append(totalResources).
                 append(totalRoles).
                 append(confCompleteness).
-                append(asyncConnectorExecutor).
-                append(propagationTaskExecutor).
+                append(taskExecutorInfos).
                 build();
     }
 
@@ -307,8 +300,7 @@ public class NumbersInfo implements BaseBean {
                 append(totalAny2, other.totalAny2).
                 append(any2ByRealm, other.any2ByRealm).
                 append(confCompleteness, other.confCompleteness).
-                append(asyncConnectorExecutor, other.asyncConnectorExecutor).
-                append(propagationTaskExecutor, other.propagationTaskExecutor).
+                append(taskExecutorInfos, other.taskExecutorInfos).
                 build();
     }
 }
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/info/PlatformInfo.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/info/PlatformInfo.java
index a58ee1d070..c81cd389e7 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/info/PlatformInfo.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/info/PlatformInfo.java
@@ -32,211 +32,6 @@ public class PlatformInfo implements BaseBean {
 
     private static final long serialVersionUID = -7941853999417673827L;
 
-    public static class ProvisioningInfo implements BaseBean {
-
-        private static final long serialVersionUID = 533340357732839568L;
-
-        private String propagationTaskExecutor;
-
-        private String virAttrCache;
-
-        private String anyObjectProvisioningManager;
-
-        private String userProvisioningManager;
-
-        private String groupProvisioningManager;
-
-        private String notificationManager;
-
-        private String auditManager;
-
-        public String getPropagationTaskExecutor() {
-            return propagationTaskExecutor;
-        }
-
-        public void setPropagationTaskExecutor(final String propagationTaskExecutor) {
-            this.propagationTaskExecutor = propagationTaskExecutor;
-        }
-
-        public String getVirAttrCache() {
-            return virAttrCache;
-        }
-
-        public void setVirAttrCache(final String virAttrCache) {
-            this.virAttrCache = virAttrCache;
-        }
-
-        public String getAnyObjectProvisioningManager() {
-            return anyObjectProvisioningManager;
-        }
-
-        public void setAnyObjectProvisioningManager(final String anyObjectProvisioningManager) {
-            this.anyObjectProvisioningManager = anyObjectProvisioningManager;
-        }
-
-        public String getUserProvisioningManager() {
-            return userProvisioningManager;
-        }
-
-        public void setUserProvisioningManager(final String userProvisioningManager) {
-            this.userProvisioningManager = userProvisioningManager;
-        }
-
-        public String getGroupProvisioningManager() {
-            return groupProvisioningManager;
-        }
-
-        public void setGroupProvisioningManager(final String groupProvisioningManager) {
-            this.groupProvisioningManager = groupProvisioningManager;
-        }
-
-        public String getNotificationManager() {
-            return notificationManager;
-        }
-
-        public void setNotificationManager(final String notificationManager) {
-            this.notificationManager = notificationManager;
-        }
-
-        public String getAuditManager() {
-            return auditManager;
-        }
-
-        public void setAuditManager(final String auditManager) {
-            this.auditManager = auditManager;
-        }
-    }
-
-    public static class WorkflowInfo implements BaseBean {
-
-        private static final long serialVersionUID = 6736937721099195324L;
-
-        private String anyObjectWorkflowAdapter;
-
-        private String userWorkflowAdapter;
-
-        private String groupWorkflowAdapter;
-
-        public String getAnyObjectWorkflowAdapter() {
-            return anyObjectWorkflowAdapter;
-        }
-
-        public void setAnyObjectWorkflowAdapter(final String anyObjectWorkflowAdapter) {
-            this.anyObjectWorkflowAdapter = anyObjectWorkflowAdapter;
-        }
-
-        public String getUserWorkflowAdapter() {
-            return userWorkflowAdapter;
-        }
-
-        public void setUserWorkflowAdapter(final String userWorkflowAdapter) {
-            this.userWorkflowAdapter = userWorkflowAdapter;
-        }
-
-        public String getGroupWorkflowAdapter() {
-            return groupWorkflowAdapter;
-        }
-
-        public void setGroupWorkflowAdapter(final String groupWorkflowAdapter) {
-            this.groupWorkflowAdapter = groupWorkflowAdapter;
-        }
-    }
-
-    public static class PersistenceInfo implements BaseBean {
-
-        private static final long serialVersionUID = 2902980556801069487L;
-
-        private String entityFactory;
-
-        private String plainSchemaDAO;
-
-        private String plainAttrDAO;
-
-        private String plainAttrValueDAO;
-
-        private String anySearchDAO;
-
-        private String userDAO;
-
-        private String groupDAO;
-
-        private String anyObjectDAO;
-
-        public String getEntityFactory() {
-            return entityFactory;
-        }
-
-        public void setEntityFactory(final String entityFactory) {
-            this.entityFactory = entityFactory;
-        }
-
-        public String getPlainSchemaDAO() {
-            return plainSchemaDAO;
-        }
-
-        public void setPlainSchemaDAO(final String plainSchemaDAO) {
-            this.plainSchemaDAO = plainSchemaDAO;
-        }
-
-        public String getPlainAttrDAO() {
-            return plainAttrDAO;
-        }
-
-        public void setPlainAttrDAO(final String plainAttrDAO) {
-            this.plainAttrDAO = plainAttrDAO;
-        }
-
-        public String getPlainAttrValueDAO() {
-            return plainAttrValueDAO;
-        }
-
-        public void setPlainAttrValueDAO(final String plainAttrValueDAO) {
-            this.plainAttrValueDAO = plainAttrValueDAO;
-        }
-
-        public String getAnySearchDAO() {
-            return anySearchDAO;
-        }
-
-        public void setAnySearchDAO(final String anySearchDAO) {
-            this.anySearchDAO = anySearchDAO;
-        }
-
-        public String getUserDAO() {
-            return userDAO;
-        }
-
-        public void setUserDAO(final String userDAO) {
-            this.userDAO = userDAO;
-        }
-
-        public String getGroupDAO() {
-            return groupDAO;
-        }
-
-        public void setGroupDAO(final String groupDAO) {
-            this.groupDAO = groupDAO;
-        }
-
-        public String getAnyObjectDAO() {
-            return anyObjectDAO;
-        }
-
-        public void setAnyObjectDAO(final String anyObjectDAO) {
-            this.anyObjectDAO = anyObjectDAO;
-        }
-    }
-
-    private String keymasterConfParamOps;
-
-    private String keymasterServiceOps;
-
-    private final ProvisioningInfo provisioningInfo = new ProvisioningInfo();
-
-    private final WorkflowInfo workflowInfo = new WorkflowInfo();
-
-    private final PersistenceInfo persistenceInfo = new PersistenceInfo();
-
     private boolean selfRegAllowed;
 
     private boolean pwdResetAllowed;
@@ -245,8 +40,6 @@ public class PlatformInfo implements BaseBean {
 
     private final Set<String> connIdLocations = new HashSet<>();
 
-    private String passwordGenerator;
-
     private final List<String> anyTypes = new ArrayList<>();
 
     private final List<String> userClasses = new ArrayList<>();
@@ -261,26 +54,6 @@ public class PlatformInfo implements BaseBean {
 
     private final Set<JavaImplInfo> javaImplInfos = new HashSet<>();
 
-    public String getKeymasterConfParamOps() {
-        return keymasterConfParamOps;
-    }
-
-    public String getKeymasterServiceOps() {
-        return keymasterServiceOps;
-    }
-
-    public ProvisioningInfo getProvisioningInfo() {
-        return provisioningInfo;
-    }
-
-    public WorkflowInfo getWorkflowInfo() {
-        return workflowInfo;
-    }
-
-    public PersistenceInfo getPersistenceInfo() {
-        return persistenceInfo;
-    }
-
     public boolean isSelfRegAllowed() {
         return selfRegAllowed;
     }
@@ -299,14 +72,6 @@ public class PlatformInfo implements BaseBean {
         return connIdLocations;
     }
 
-    public String getPasswordGenerator() {
-        return passwordGenerator;
-    }
-
-    public void setPasswordGenerator(final String passwordGenerator) {
-        this.passwordGenerator = passwordGenerator;
-    }
-
     @JacksonXmlElementWrapper(localName = "anyTypes")
     @JacksonXmlProperty(localName = "anyType")
     public List<String> getAnyTypes() {
@@ -354,14 +119,6 @@ public class PlatformInfo implements BaseBean {
         return javaImplInfos;
     }
 
-    public void setKeymasterConfParamOps(final String keymasterConfParamOps) {
-        this.keymasterConfParamOps = keymasterConfParamOps;
-    }
-
-    public void setKeymasterServiceOps(final String keymasterServiceOps) {
-        this.keymasterServiceOps = keymasterServiceOps;
-    }
-
     public void setSelfRegAllowed(final boolean selfRegAllowed) {
         this.selfRegAllowed = selfRegAllowed;
     }
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
index 555488be6f..3586e23cae 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/ClientExceptionType.java
@@ -29,7 +29,6 @@ public enum ClientExceptionType {
     DataIntegrityViolation(Response.Status.CONFLICT),
     EntityExists(Response.Status.CONFLICT),
     GenericPersistence(Response.Status.BAD_REQUEST),
-    HasChildren(Response.Status.BAD_REQUEST),
     InvalidAccessToken(Response.Status.INTERNAL_SERVER_ERROR),
     InvalidPrivilege(Response.Status.BAD_REQUEST),
     InvalidImplementation(Response.Status.BAD_REQUEST),
diff --git a/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyImplementationLookup.java b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyImplementationLookup.java
index 7cf8a155f3..ec237ee3d1 100644
--- a/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyImplementationLookup.java
+++ b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyImplementationLookup.java
@@ -82,9 +82,4 @@ public class DummyImplementationLookup implements ImplementationLookup {
 
         return null;
     }
-
-    @Override
-    public Set<Class<?>> getAuditAppenderClasses() {
-        return Set.of();
-    }
 }
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
index 18d31d2664..38d054e81d 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
@@ -45,6 +45,7 @@ import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.common.lib.types.MatchingRule;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
+import org.apache.syncope.core.logic.audit.AuditAppender;
 import org.apache.syncope.core.logic.init.AuditLoader;
 import org.apache.syncope.core.persistence.api.dao.AuditConfDAO;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
@@ -57,6 +58,7 @@ import org.apache.syncope.core.provisioning.api.data.AuditDataBinder;
 import org.apache.syncope.core.provisioning.java.pushpull.PullJobDelegate;
 import org.apache.syncope.core.provisioning.java.pushpull.PushJobDelegate;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.springframework.context.ApplicationContext;
 import org.springframework.core.io.Resource;
 import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
 import org.springframework.core.io.support.ResourcePatternResolver;
@@ -72,8 +74,6 @@ public class AuditLogic extends AbstractTransactionalLogic<AuditConfTO> {
 
     protected static final List<EventCategory> EVENTS = new ArrayList<>();
 
-    protected final AuditLoader auditLoader;
-
     protected final AuditConfDAO auditConfDAO;
 
     protected final ExternalResourceDAO resourceDAO;
@@ -84,20 +84,22 @@ public class AuditLogic extends AbstractTransactionalLogic<AuditConfTO> {
 
     protected final AuditManager auditManager;
 
+    protected final ApplicationContext ctx;
+
     public AuditLogic(
-            final AuditLoader auditLoader,
             final AuditConfDAO auditConfDAO,
             final ExternalResourceDAO resourceDAO,
             final EntityFactory entityFactory,
             final AuditDataBinder binder,
-            final AuditManager auditManager) {
+            final AuditManager auditManager,
+            final ApplicationContext ctx) {
 
-        this.auditLoader = auditLoader;
         this.auditConfDAO = auditConfDAO;
         this.resourceDAO = resourceDAO;
         this.entityFactory = entityFactory;
         this.binder = binder;
         this.auditManager = auditManager;
+        this.ctx = ctx;
     }
 
     @PreAuthorize("hasRole('" + IdRepoEntitlement.AUDIT_LIST + "')")
@@ -141,17 +143,17 @@ public class AuditLogic extends AbstractTransactionalLogic<AuditConfTO> {
     protected void setLevel(final String key, final Level level) {
         String auditLoggerName = AuditLoggerName.getAuditEventLoggerName(AuthContextUtils.getDomain(), key);
 
-        LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
-        LoggerConfig logConf = ctx.getConfiguration().getLoggerConfig(auditLoggerName);
+        LoggerContext logCtx = (LoggerContext) LogManager.getContext(false);
+        LoggerConfig logConf = logCtx.getConfiguration().getLoggerConfig(auditLoggerName);
 
         // SYNCOPE-1144 For each custom audit appender class add related appenders to log4j logger
-        auditLoader.auditAppenders(AuthContextUtils.getDomain()).stream().
+        ctx.getBeansOfType(AuditAppender.class).values().stream().
                 filter(appender -> appender.getEvents().stream().
                 anyMatch(event -> key.equalsIgnoreCase(event.toAuditKey()))).
-                forEach(auditAppender -> AuditLoader.addAppenderToContext(ctx, auditAppender, logConf));
+                forEach(auditAppender -> AuditLoader.addAppenderToLoggerContext(logCtx, auditAppender, logConf));
 
         logConf.setLevel(level);
-        ctx.updateLoggers();
+        logCtx.updateLoggers();
     }
 
     @PreAuthorize("hasRole('" + IdRepoEntitlement.AUDIT_LIST + "') "
@@ -270,11 +272,11 @@ public class AuditLogic extends AbstractTransactionalLogic<AuditConfTO> {
             final String subcategory,
             final List<String> events,
             final AuditElements.Result result,
-            final List<OrderByClause> orderByClauses) {
+            final List<OrderByClause> orderBy) {
 
         int count = auditConfDAO.countEntries(entityKey, type, category, subcategory, events, result);
         List<AuditEntry> matching = auditConfDAO.searchEntries(
-                entityKey, page, size, type, category, subcategory, events, result, orderByClauses);
+                entityKey, page, size, type, category, subcategory, events, result, orderBy);
         return Pair.of(count, matching);
     }
 
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java
index 9a4e62889a..d04964193a 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/IdRepoLogicContext.java
@@ -18,13 +18,23 @@
  */
 package org.apache.syncope.core.logic;
 
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.LoggerConfig;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+import org.apache.syncope.core.logic.audit.AuditAppender;
+import org.apache.syncope.core.logic.audit.JdbcAuditAppender;
 import org.apache.syncope.core.logic.init.AuditAccessor;
 import org.apache.syncope.core.logic.init.AuditLoader;
 import org.apache.syncope.core.logic.init.ClassPathScanImplementationLookup;
 import org.apache.syncope.core.logic.init.EntitlementAccessor;
 import org.apache.syncope.core.logic.init.IdRepoEntitlementLoader;
 import org.apache.syncope.core.logic.init.IdRepoImplementationTypeLoader;
+import org.apache.syncope.core.persistence.api.DomainHolder;
 import org.apache.syncope.core.persistence.api.ImplementationLookup;
 import org.apache.syncope.core.persistence.api.content.ContentExporter;
 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
@@ -98,14 +108,13 @@ import org.apache.syncope.core.workflow.api.AnyObjectWorkflowAdapter;
 import org.apache.syncope.core.workflow.api.GroupWorkflowAdapter;
 import org.apache.syncope.core.workflow.api.UserWorkflowAdapter;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.EnableAspectJAutoProxy;
 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
 
 @EnableAspectJAutoProxy(proxyTargetClass = false)
-@EnableConfigurationProperties(LogicProperties.class)
 @Configuration(proxyBeanMethods = false)
 public class IdRepoLogicContext {
 
@@ -134,10 +143,29 @@ public class IdRepoLogicContext {
     @Bean
     public AuditLoader auditLoader(
             final AuditAccessor auditAccessor,
-            final ImplementationLookup implementationLookup,
-            final LogicProperties logicProperties) {
+            final ApplicationContext ctx) {
 
-        return new AuditLoader(auditAccessor, implementationLookup, logicProperties);
+        return new AuditLoader(auditAccessor, ctx);
+    }
+
+    @ConditionalOnMissingBean(name = "defaultAuditAppenders")
+    @Bean
+    public List<AuditAppender> defaultAuditAppenders(final DomainHolder domainHolder) {
+        List<AuditAppender> auditAppenders = new ArrayList<>();
+
+        LoggerContext logCtx = (LoggerContext) LogManager.getContext(false);
+        domainHolder.getDomains().forEach((domain, dataSource) -> {
+            AuditAppender appender = new JdbcAuditAppender(domain, dataSource);
+
+            LoggerConfig logConf = new LoggerConfig(AuditLoggerName.getAuditLoggerName(domain), null, false);
+            logConf.addAppender(appender.getTargetAppender(), Level.DEBUG, null);
+            logConf.setLevel(Level.DEBUG);
+            logCtx.getConfiguration().addLogger(logConf.getName(), logConf);
+
+            auditAppenders.add(appender);
+        });
+
+        return auditAppenders;
     }
 
     @ConditionalOnMissingBean
@@ -220,20 +248,20 @@ public class IdRepoLogicContext {
     @ConditionalOnMissingBean
     @Bean
     public AuditLogic auditLogic(
-            final AuditManager auditManager,
-            final AuditLoader auditLoader,
             final AuditConfDAO auditConfDAO,
             final ExternalResourceDAO externalResourceDAO,
             final EntityFactory entityFactory,
-            final AuditDataBinder binder) {
+            final AuditDataBinder binder,
+            final AuditManager auditManager,
+            final ApplicationContext ctx) {
 
         return new AuditLogic(
-                auditLoader,
                 auditConfDAO,
                 externalResourceDAO,
                 entityFactory,
                 binder,
-                auditManager);
+                auditManager,
+                ctx);
     }
 
     @ConditionalOnMissingBean
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/LogicProperties.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/LogicProperties.java
deleted file mode 100644
index 0b69910777..0000000000
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/LogicProperties.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.syncope.core.logic;
-
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-@ConfigurationProperties("logic")
-public class LogicProperties {
-
-    private boolean enableJDBCAuditAppender = true;
-
-    public boolean isEnableJDBCAuditAppender() {
-        return enableJDBCAuditAppender;
-    }
-
-    public void setEnableJDBCAuditAppender(final boolean enableJDBCAuditAppender) {
-        this.enableJDBCAuditAppender = enableJDBCAuditAppender;
-    }
-}
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 cce7a068fc..5c259913ee 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
@@ -222,7 +222,7 @@ public class RealmLogic extends AbstractTransactionalLogic<RealmTO> {
                 orElseThrow(() -> new NotFoundException("Realm " + fullPath));
 
         if (!realmDAO.findChildren(realm).isEmpty()) {
-            throw SyncopeClientException.build(ClientExceptionType.HasChildren);
+            throw SyncopeClientException.build(ClientExceptionType.RealmContains);
         }
 
         Set<String> adminRealms = Set.of(realm.getFullPath());
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/audit/AuditAppender.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/audit/AuditAppender.java
index d81daa668d..9a8d8122b0 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/audit/AuditAppender.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/audit/AuditAppender.java
@@ -32,8 +32,6 @@ import org.apache.syncope.common.lib.types.AuditLoggerName;
  */
 public interface AuditAppender {
 
-    void init(String domain);
-
     default Set<AuditLoggerName> getEvents() {
         return Set.of();
     }
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/audit/DefaultAuditAppender.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/audit/DefaultAuditAppender.java
index a78f82f474..a18d7c7d30 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/audit/DefaultAuditAppender.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/audit/DefaultAuditAppender.java
@@ -29,18 +29,14 @@ import org.apache.logging.log4j.core.Appender;
  */
 public abstract class DefaultAuditAppender implements AuditAppender {
 
-    protected String domain;
+    protected final String domain;
 
     protected Appender targetAppender;
 
-    @Override
-    public void init(final String domain) {
+    protected DefaultAuditAppender(final String domain) {
         this.domain = domain;
-        initTargetAppender();
     }
 
-    protected abstract void initTargetAppender();
-
     @Override
     public Appender getTargetAppender() {
         return targetAppender;
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/audit/DefaultRewriteAuditAppender.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/audit/DefaultRewriteAuditAppender.java
index ed62206e94..008752226a 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/audit/DefaultRewriteAuditAppender.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/audit/DefaultRewriteAuditAppender.java
@@ -39,9 +39,8 @@ public abstract class DefaultRewriteAuditAppender extends DefaultAuditAppender {
 
     protected RewriteAppender rewriteAppender;
 
-    @Override
-    public void init(final String domain) {
-        super.init(domain);
+    public DefaultRewriteAuditAppender(final String domain) {
+        super(domain);
 
         rewriteAppender = RewriteAppender.createAppender(
                 getTargetAppenderName() + "_rewrite",
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/audit/JdbcAuditAppender.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/audit/JdbcAuditAppender.java
index 2229699e69..4ecce56d9b 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/audit/JdbcAuditAppender.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/audit/JdbcAuditAppender.java
@@ -21,6 +21,7 @@ package org.apache.syncope.core.logic.audit;
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.sql.Timestamp;
+import java.util.Optional;
 import javax.sql.DataSource;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.core.Appender;
@@ -28,54 +29,49 @@ import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.appender.db.ColumnMapping;
 import org.apache.logging.log4j.core.appender.db.jdbc.AbstractConnectionSource;
 import org.apache.logging.log4j.core.appender.db.jdbc.JdbcAppender;
-import org.apache.syncope.core.persistence.api.DomainHolder;
 import org.apache.syncope.core.persistence.api.dao.AuditConfDAO;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jdbc.datasource.DataSourceUtils;
 
 public class JdbcAuditAppender extends DefaultAuditAppender {
 
-    @Autowired
-    protected DomainHolder domainHolder;
+    public JdbcAuditAppender(final String domain, final DataSource domainDataSource) {
+        super(domain);
 
-    @Override
-    protected void initTargetAppender() {
-        LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
+        LoggerContext logCtx = (LoggerContext) LogManager.getContext(false);
 
         ColumnMapping[] columnMappings = {
             ColumnMapping.newBuilder().
-            setConfiguration(ctx.getConfiguration()).setName("EVENT_DATE").setType(Timestamp.class).build(),
+            setConfiguration(logCtx.getConfiguration()).setName("EVENT_DATE").setType(Timestamp.class).build(),
             ColumnMapping.newBuilder().
-            setConfiguration(ctx.getConfiguration()).setName("LOGGER_LEVEL").setPattern("%level").build(),
+            setConfiguration(logCtx.getConfiguration()).setName("LOGGER_LEVEL").setPattern("%level").build(),
             ColumnMapping.newBuilder().
-            setConfiguration(ctx.getConfiguration()).setName("LOGGER").setPattern("%logger").build(),
+            setConfiguration(logCtx.getConfiguration()).setName("LOGGER").setPattern("%logger").build(),
             ColumnMapping.newBuilder().
-            setConfiguration(ctx.getConfiguration()).
+            setConfiguration(logCtx.getConfiguration()).
             setName(AuditConfDAO.AUDIT_ENTRY_MESSAGE_COLUMN).setPattern("%message").build(),
             ColumnMapping.newBuilder().
-            setConfiguration(ctx.getConfiguration()).setName("THROWABLE").setPattern("%ex{full}").build()
+            setConfiguration(logCtx.getConfiguration()).setName("THROWABLE").setPattern("%ex{full}").build()
         };
 
-        Appender appender = ctx.getConfiguration().getAppender("audit_for_" + domain);
-        if (appender == null) {
-            appender = JdbcAppender.newBuilder().
-                    setName("audit_for_" + domain).
-                    setIgnoreExceptions(false).
-                    setConnectionSource(new DataSourceConnectionSource(domain, domainHolder.getDomains().get(domain))).
-                    setBufferSize(0).
-                    setTableName(AuditConfDAO.AUDIT_ENTRY_TABLE).
-                    setColumnMappings(columnMappings).
-                    build();
-            appender.start();
-            ctx.getConfiguration().addAppender(appender);
-        }
-        targetAppender = appender;
+        targetAppender = Optional.ofNullable(logCtx.getConfiguration().<Appender>getAppender(getTargetAppenderName())).
+                orElseGet(() -> {
+                    JdbcAppender a = JdbcAppender.newBuilder().
+                            setName(getTargetAppenderName()).
+                            setIgnoreExceptions(false).
+                            setConnectionSource(new DataSourceConnectionSource(domain, domainDataSource)).
+                            setBufferSize(0).
+                            setTableName(AuditConfDAO.AUDIT_ENTRY_TABLE).
+                            setColumnMappings(columnMappings).
+                            build();
+                    a.start();
+                    logCtx.getConfiguration().addAppender(a);
+                    return a;
+                });
     }
 
     @Override
     public String getTargetAppenderName() {
-        // not used
-        return null;
+        return "audit_for_" + domain;
     }
 
     protected static class DataSourceConnectionSource extends AbstractConnectionSource {
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/AuditLoader.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/AuditLoader.java
index 09ac732c8f..f906c52ed8 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/AuditLoader.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/AuditLoader.java
@@ -18,9 +18,7 @@
  */
 package org.apache.syncope.core.logic.init;
 
-import java.util.List;
 import java.util.Optional;
-import java.util.stream.Collectors;
 import javax.sql.DataSource;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogManager;
@@ -29,34 +27,43 @@ import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.appender.rewrite.RewriteAppender;
 import org.apache.logging.log4j.core.config.LoggerConfig;
 import org.apache.syncope.common.lib.types.AuditLoggerName;
-import org.apache.syncope.core.logic.LogicProperties;
 import org.apache.syncope.core.logic.audit.AuditAppender;
-import org.apache.syncope.core.logic.audit.JdbcAuditAppender;
-import org.apache.syncope.core.persistence.api.ImplementationLookup;
 import org.apache.syncope.core.persistence.api.SyncopeCoreLoader;
-import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
-import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.context.ApplicationContext;
 
 public class AuditLoader implements SyncopeCoreLoader {
 
     protected static final String ROOT_LOGGER = "ROOT";
 
-    protected final AuditAccessor auditAccessor;
+    public static void addAppenderToLoggerContext(
+            final LoggerContext ctx,
+            final AuditAppender auditAppender,
+            final LoggerConfig eventLogConf) {
+
+        Appender targetAppender = ctx.getConfiguration().getAppender(auditAppender.getTargetAppenderName());
+        if (targetAppender == null) {
+            targetAppender = auditAppender.getTargetAppender();
+        }
+        targetAppender.start();
+        ctx.getConfiguration().addAppender(targetAppender);
 
-    protected final ImplementationLookup implementationLookup;
+        Optional<RewriteAppender> rewriteAppender = auditAppender.getRewriteAppender();
+        if (rewriteAppender.isPresent()) {
+            rewriteAppender.get().start();
+            eventLogConf.addAppender(rewriteAppender.get(), Level.DEBUG, null);
+        } else {
+            eventLogConf.addAppender(targetAppender, Level.DEBUG, null);
+        }
+    }
 
-    protected final LogicProperties props;
+    protected final AuditAccessor auditAccessor;
 
-    public AuditLoader(
-            final AuditAccessor auditAccessor,
-            final ImplementationLookup implementationLookup,
-            final LogicProperties props) {
+    protected final ApplicationContext ctx;
 
+    public AuditLoader(final AuditAccessor auditAccessor, final ApplicationContext ctx) {
         this.auditAccessor = auditAccessor;
-        this.implementationLookup = implementationLookup;
-        this.props = props;
+        this.ctx = ctx;
     }
 
     @Override
@@ -66,77 +73,32 @@ public class AuditLoader implements SyncopeCoreLoader {
 
     @Override
     public void load(final String domain, final DataSource datasource) {
-        LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
-
-        if (props.isEnableJDBCAuditAppender()) {
-            JdbcAuditAppender jdbcAuditAppender = (JdbcAuditAppender) ApplicationContextProvider.getBeanFactory().
-                    createBean(JdbcAuditAppender.class, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true);
-            jdbcAuditAppender.init(domain);
-
-            LoggerConfig logConf = new LoggerConfig(AuditLoggerName.getAuditLoggerName(domain), null, false);
-            logConf.addAppender(jdbcAuditAppender.getTargetAppender(), Level.DEBUG, null);
-            logConf.setLevel(Level.DEBUG);
-            ctx.getConfiguration().addLogger(logConf.getName(), logConf);
-        }
+        LoggerContext logCtx = (LoggerContext) LogManager.getContext(false);
 
         // SYNCOPE-1144 For each custom audit appender class add related appenders to log4j logger
-        auditAppenders(domain).forEach(auditAppender -> auditAppender.getEvents().stream().
+        ctx.getBeansOfType(AuditAppender.class).values().forEach(auditAppender -> auditAppender.getEvents().stream().
                 map(event -> AuditLoggerName.getAuditEventLoggerName(domain, event.toAuditKey())).
                 forEach(domainAuditLoggerName -> {
-                    LoggerConfig eventLogConf = ctx.getConfiguration().getLoggerConfig(domainAuditLoggerName);
+                    LoggerConfig eventLogConf = logCtx.getConfiguration().getLoggerConfig(domainAuditLoggerName);
                     boolean isRootLogConf = LogManager.ROOT_LOGGER_NAME.equals(eventLogConf.getName());
+
                     if (isRootLogConf) {
                         eventLogConf = new LoggerConfig(domainAuditLoggerName, null, false);
                     }
-                    addAppenderToContext(ctx, auditAppender, eventLogConf);
+
+                    addAppenderToLoggerContext(logCtx, auditAppender, eventLogConf);
                     eventLogConf.setLevel(Level.DEBUG);
+
                     if (isRootLogConf) {
-                        ctx.getConfiguration().addLogger(domainAuditLoggerName, eventLogConf);
+                        logCtx.getConfiguration().addLogger(domainAuditLoggerName, eventLogConf);
                     }
                 }));
 
         AuthContextUtils.callAsAdmin(domain, () -> {
-            auditAccessor.synchronizeLoggingWithAudit(ctx);
+            auditAccessor.synchronizeLoggingWithAudit(logCtx);
             return null;
         });
 
-        ctx.updateLoggers();
-    }
-
-    public List<AuditAppender> auditAppenders(final String domain) throws BeansException {
-        return implementationLookup.getAuditAppenderClasses().stream().map(clazz -> {
-            AuditAppender auditAppender;
-            if (ApplicationContextProvider.getBeanFactory().containsSingleton(clazz.getName())) {
-                auditAppender = (AuditAppender) ApplicationContextProvider.getBeanFactory().
-                        getSingleton(clazz.getName());
-            } else {
-                auditAppender = (AuditAppender) ApplicationContextProvider.getBeanFactory().
-                        createBean(clazz, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true);
-                ApplicationContextProvider.getBeanFactory().registerSingleton(clazz.getName(), auditAppender);
-                auditAppender.init(domain);
-            }
-            return auditAppender;
-        }).collect(Collectors.toList());
-    }
-
-    public static void addAppenderToContext(
-            final LoggerContext ctx,
-            final AuditAppender auditAppender,
-            final LoggerConfig eventLogConf) {
-
-        Appender targetAppender = ctx.getConfiguration().getAppender(auditAppender.getTargetAppenderName());
-        if (targetAppender == null) {
-            targetAppender = auditAppender.getTargetAppender();
-        }
-        targetAppender.start();
-        ctx.getConfiguration().addAppender(targetAppender);
-
-        Optional<RewriteAppender> rewriteAppender = auditAppender.getRewriteAppender();
-        if (rewriteAppender.isPresent()) {
-            rewriteAppender.get().start();
-            eventLogConf.addAppender(rewriteAppender.get(), Level.DEBUG, null);
-        } else {
-            eventLogConf.addAppender(targetAppender, Level.DEBUG, null);
-        }
+        logCtx.updateLoggers();
     }
 }
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
index b1aa633cf8..8036954740 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
@@ -35,8 +35,6 @@ import org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.syncope.common.lib.types.ImplementationTypesHolder;
 import org.apache.syncope.core.logic.api.Command;
 import org.apache.syncope.core.logic.api.LogicActions;
-import org.apache.syncope.core.logic.audit.AuditAppender;
-import org.apache.syncope.core.logic.audit.JdbcAuditAppender;
 import org.apache.syncope.core.persistence.api.ImplementationLookup;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValueValidator;
 import org.apache.syncope.core.persistence.api.dao.AccountRule;
@@ -92,8 +90,6 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
 
     private Map<Class<? extends PushCorrelationRuleConf>, Class<? extends PushCorrelationRule>> pushCRClasses;
 
-    private Set<Class<?>> auditAppenderClasses;
-
     @Override
     public int getOrder() {
         return Ordered.HIGHEST_PRECEDENCE + 1;
@@ -129,7 +125,6 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
         passwordRuleClasses = new HashMap<>();
         pullCRClasses = new HashMap<>();
         pushCRClasses = new HashMap<>();
-        auditAppenderClasses = new HashSet<>();
 
         scanner.findCandidateComponents(getBasePackage()).forEach(bd -> {
             try {
@@ -234,13 +229,6 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
                     classNames.get(IdRepoImplementationType.RECIPIENTS_PROVIDER).add(bd.getBeanClassName());
                 }
 
-                if (AuditAppender.class.isAssignableFrom(clazz)
-                        && !JdbcAuditAppender.class.equals(clazz) && !isAbstractClazz) {
-
-                    classNames.get(IdRepoImplementationType.AUDIT_APPENDER).add(clazz.getName());
-                    auditAppenderClasses.add(clazz);
-                }
-
                 if (ProvisionSorter.class.isAssignableFrom(clazz) && !isAbstractClazz) {
                     classNames.get(IdMImplementationType.PROVISION_SORTER).add(bd.getBeanClassName());
                 }
@@ -262,7 +250,6 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
         passwordRuleClasses = Collections.unmodifiableMap(passwordRuleClasses);
         pullCRClasses = Collections.unmodifiableMap(pullCRClasses);
         pushCRClasses = Collections.unmodifiableMap(pushCRClasses);
-        auditAppenderClasses = Collections.unmodifiableSet(auditAppenderClasses);
     }
 
     @Override
@@ -309,9 +296,4 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
 
         return pushCRClasses.get(correlationRuleConfClass);
     }
-
-    @Override
-    public Set<Class<?>> getAuditAppenderClasses() {
-        return auditAppenderClasses;
-    }
 }
diff --git a/core/idrepo/logic/src/test/java/org/apache/syncope/core/logic/DummyImplementationLookup.java b/core/idrepo/logic/src/test/java/org/apache/syncope/core/logic/DummyImplementationLookup.java
index 28c68461c1..92acd2764d 100644
--- a/core/idrepo/logic/src/test/java/org/apache/syncope/core/logic/DummyImplementationLookup.java
+++ b/core/idrepo/logic/src/test/java/org/apache/syncope/core/logic/DummyImplementationLookup.java
@@ -88,9 +88,4 @@ public class DummyImplementationLookup implements ImplementationLookup {
 
         return null;
     }
-
-    @Override
-    public Set<Class<?>> getAuditAppenderClasses() {
-        return Set.of();
-    }
 }
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdRepoRESTCXFContext.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdRepoRESTCXFContext.java
index aebfacf737..ab01ca51ad 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdRepoRESTCXFContext.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdRepoRESTCXFContext.java
@@ -130,6 +130,7 @@ import org.apache.syncope.core.rest.cxf.service.SyncopeServiceImpl;
 import org.apache.syncope.core.rest.cxf.service.TaskServiceImpl;
 import org.apache.syncope.core.rest.cxf.service.UserSelfServiceImpl;
 import org.apache.syncope.core.rest.cxf.service.UserServiceImpl;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
@@ -145,7 +146,6 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 @Configuration(proxyBeanMethods = false)
 public class IdRepoRESTCXFContext {
 
-    @ConditionalOnMissingBean
     @Bean
     public ThreadPoolTaskExecutor batchExecutor(final RESTProperties props) {
         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
@@ -480,6 +480,7 @@ public class IdRepoRESTCXFContext {
     public SyncopeService syncopeService(
             final Bus bus,
             final SyncopeLogic syncopeLogic,
+            @Qualifier("batchExecutor")
             final ThreadPoolTaskExecutor batchExecutor,
             final BatchDAO batchDAO,
             final EntityFactory entityFactory) {
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java
index 0fea24cc55..fd94f32420 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/ImplementationLookup.java
@@ -50,6 +50,4 @@ public interface ImplementationLookup extends SyncopeCoreLoader {
 
     Class<? extends PushCorrelationRule> getPushCorrelationRuleClass(
             Class<? extends PushCorrelationRuleConf> pushCorrelationRuleConfClass);
-
-    Set<Class<?>> getAuditAppenderClasses();
 }
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuditConfDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuditConfDAO.java
index 96d215838e..4e060e5499 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuditConfDAO.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuditConfDAO.java
@@ -49,11 +49,11 @@ public interface AuditConfDAO extends DAO<AuditConf> {
     List<AuditEntry> searchEntries(
             String entityKey,
             int page,
-            int size,
+            int itemsPerPage,
             AuditElements.EventCategoryType type,
             String category,
             String subcategory,
             List<String> events,
             AuditElements.Result result,
-            List<OrderByClause> orderByClauses);
+            List<OrderByClause> orderBy);
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAuditConfDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAuditConfDAO.java
index 6384a3df73..2f68b4cf2d 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAuditConfDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAuditConfDAO.java
@@ -165,7 +165,7 @@ public class JPAAuditConfDAO extends AbstractDAO<AuditConf> implements AuditConf
             final String subcategory,
             final List<String> events,
             final AuditElements.Result result,
-            final List<OrderByClause> orderByClauses) {
+            final List<OrderByClause> orderBy) {
 
         String queryString = "SELECT " + select()
                 + " FROM " + AUDIT_ENTRY_TABLE
@@ -176,9 +176,9 @@ public class JPAAuditConfDAO extends AbstractDAO<AuditConf> implements AuditConf
                         result(result).
                         events(events).
                         build();
-        if (!orderByClauses.isEmpty()) {
-            queryString += " ORDER BY " + orderByClauses.stream().
-                    map(orderBy -> orderBy.getField() + ' ' + orderBy.getDirection().name()).
+        if (!orderBy.isEmpty()) {
+            queryString += " ORDER BY " + orderBy.stream().
+                    map(clause -> clause.getField() + ' ' + clause.getDirection().name()).
                     collect(Collectors.joining(","));
         }
 
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java
index 9ccb135da8..9118e68517 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/DummyImplementationLookup.java
@@ -86,9 +86,4 @@ public class DummyImplementationLookup implements ImplementationLookup {
 
         return DefaultPushCorrelationRule.class;
     }
-
-    @Override
-    public Set<Class<?>> getAuditAppenderClasses() {
-        return Set.of();
-    }
 }
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java
index 963ee27de5..3f019807e9 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/serialization/POJOHelper.java
@@ -107,6 +107,18 @@ public final class POJOHelper {
 
         return result;
     }
+    
+    public static <T extends Object> T convertValue(final Object value, final Class<T> reference) {
+        T result = null;
+
+        try {
+            result = MAPPER.convertValue(value, reference);
+        } catch (Exception e) {
+            LOG.error("During conversion", e);
+        }
+
+        return result;
+    }
 
     private POJOHelper() {
     }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultAuditManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultAuditManager.java
index e63bba1ba2..80c43b0931 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultAuditManager.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultAuditManager.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.provisioning.java;
 
 import java.time.OffsetDateTime;
 import java.util.Arrays;
+import java.util.Optional;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.SerializationUtils;
 import org.apache.syncope.common.lib.audit.AuditEntry;
@@ -139,12 +140,12 @@ public class DefaultAuditManager implements AuditManager {
 
         AuditLoggerName auditLoggerName = new AuditLoggerName(type, category, subcategory, event, condition);
 
-        AuditConf audit = auditConfDAO.find(auditLoggerName.toAuditKey());
-        if (audit != null && audit.isActive()) {
-            Throwable throwable = null;
-            if (output instanceof Throwable) {
-                throwable = (Throwable) output;
-            }
+        Optional.ofNullable(auditConfDAO.find(auditLoggerName.toAuditKey())).
+                filter(AuditConf::isActive).ifPresent(audit -> {
+
+            Throwable throwable = output instanceof Throwable
+                    ? (Throwable) output
+                    : null;
 
             AuditEntry auditEntry = new AuditEntry();
             auditEntry.setWho(who);
@@ -176,6 +177,6 @@ public class DefaultAuditManager implements AuditManager {
                 logger.debug(serializedAuditEntry, throwable);
                 eventLogger.debug(serializedAuditEntry, throwable);
             }
-        }
+        });
     }
 }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
index 96cebfa701..02b992f1c5 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
@@ -646,8 +646,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
             action.after(taskInfo, exec, afterObj);
         }
         // SYNCOPE-1136
-        String anyTypeKind = Optional.ofNullable(taskInfo.getAnyTypeKind()).
-                map(Enum::name).orElse("realm");
+        String anyTypeKind = Optional.ofNullable(taskInfo.getAnyTypeKind()).map(Enum::name).orElse("realm");
         String operation = taskInfo.getOperation().name().toLowerCase();
         boolean notificationsAvailable = notificationManager.notificationsAvailable(
                 AuditElements.EventCategoryType.PROPAGATION, anyTypeKind, taskInfo.getResource().getKey(), operation);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
index 815fd934ea..2352263384 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
@@ -576,7 +576,7 @@ public class DefaultRealmPullResultHandler
 
                     try {
                         if (!realmDAO.findChildren(realm).isEmpty()) {
-                            throw SyncopeClientException.build(ClientExceptionType.HasChildren);
+                            throw SyncopeClientException.build(ClientExceptionType.RealmContains);
                         }
 
                         Set<String> adminRealms = Set.of(realm.getFullPath());
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java
index 44e323a444..ec1f54d72a 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DummyImplementationLookup.java
@@ -86,9 +86,4 @@ public class DummyImplementationLookup implements ImplementationLookup {
 
         return DefaultPushCorrelationRule.class;
     }
-
-    @Override
-    public Set<Class<?>> getAuditAppenderClasses() {
-        return Set.of();
-    }
 }
diff --git a/core/spring/src/test/java/org/apache/syncope/core/spring/security/DummyImplementationLookup.java b/core/spring/src/test/java/org/apache/syncope/core/spring/security/DummyImplementationLookup.java
index e997809352..019430e81e 100644
--- a/core/spring/src/test/java/org/apache/syncope/core/spring/security/DummyImplementationLookup.java
+++ b/core/spring/src/test/java/org/apache/syncope/core/spring/security/DummyImplementationLookup.java
@@ -82,9 +82,4 @@ public class DummyImplementationLookup implements ImplementationLookup {
 
         return null;
     }
-
-    @Override
-    public Set<Class<?>> getAuditAppenderClasses() {
-        return Set.of();
-    }
 }
diff --git a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreApplication.java b/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreApplication.java
index 8e9db02976..90aaa28943 100644
--- a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreApplication.java
+++ b/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreApplication.java
@@ -21,50 +21,29 @@ package org.apache.syncope.core.starter;
 import java.util.Map;
 import org.apache.cxf.spring.boot.autoconfigure.openapi.OpenApiAutoConfiguration;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
-import org.apache.syncope.common.keymaster.client.api.ServiceOps;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
 import org.apache.syncope.common.lib.info.SystemInfo;
-import org.apache.syncope.core.logic.LogicProperties;
 import org.apache.syncope.core.persistence.api.DomainHolder;
 import org.apache.syncope.core.persistence.api.ImplementationLookup;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
-import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.EntityCacheDAO;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.NotificationDAO;
-import org.apache.syncope.core.persistence.api.dao.PlainAttrDAO;
-import org.apache.syncope.core.persistence.api.dao.PlainAttrValueDAO;
-import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.PolicyDAO;
 import org.apache.syncope.core.persistence.api.dao.RoleDAO;
 import org.apache.syncope.core.persistence.api.dao.SecurityQuestionDAO;
 import org.apache.syncope.core.persistence.api.dao.TaskDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
-import org.apache.syncope.core.persistence.api.entity.EntityFactory;
-import org.apache.syncope.core.persistence.jpa.PersistenceProperties;
-import org.apache.syncope.core.provisioning.api.AnyObjectProvisioningManager;
-import org.apache.syncope.core.provisioning.api.AuditManager;
 import org.apache.syncope.core.provisioning.api.ConnIdBundleManager;
-import org.apache.syncope.core.provisioning.api.GroupProvisioningManager;
-import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
-import org.apache.syncope.core.provisioning.api.cache.VirAttrCache;
-import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
-import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
-import org.apache.syncope.core.provisioning.java.ProvisioningProperties;
-import org.apache.syncope.core.spring.security.PasswordGenerator;
-import org.apache.syncope.core.spring.security.SecurityProperties;
 import org.apache.syncope.core.starter.actuate.DefaultSyncopeCoreInfoContributor;
 import org.apache.syncope.core.starter.actuate.DomainsHealthIndicator;
 import org.apache.syncope.core.starter.actuate.EntityCacheEndpoint;
 import org.apache.syncope.core.starter.actuate.ExternalResourcesHealthIndicator;
 import org.apache.syncope.core.starter.actuate.SyncopeCoreInfoContributor;
-import org.apache.syncope.core.workflow.api.AnyObjectWorkflowAdapter;
-import org.apache.syncope.core.workflow.api.GroupWorkflowAdapter;
-import org.apache.syncope.core.workflow.api.UserWorkflowAdapter;
 import org.springframework.beans.factory.ListableBeanFactory;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.actuate.mail.MailHealthIndicator;
@@ -80,6 +59,7 @@ import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguratio
 import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
 import org.springframework.boot.builder.SpringApplicationBuilder;
 import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.ApplicationContext;
 import org.springframework.context.PayloadApplicationEvent;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.event.EventListener;
@@ -134,85 +114,40 @@ public class SyncopeCoreApplication extends SpringBootServletInitializer {
     @ConditionalOnMissingBean
     @Bean
     public SyncopeCoreInfoContributor syncopeCoreInfoContributor(
-            final SecurityProperties securityProperties,
-            final PersistenceProperties persistenceProperties,
-            final ProvisioningProperties provisioningProperties,
-            final LogicProperties logicProperties,
             final AnyTypeDAO anyTypeDAO,
             final AnyTypeClassDAO anyTypeClassDAO,
+            final ExternalResourceDAO resourceDAO,
             final UserDAO userDAO,
             final GroupDAO groupDAO,
             final AnyObjectDAO anyObjectDAO,
-            final ExternalResourceDAO resourceDAO,
-            final ConfParamOps confParamOps,
-            final ServiceOps serviceOps,
-            final ConnIdBundleManager bundleManager,
-            final PropagationTaskExecutor propagationTaskExecutor,
-            final AnyObjectWorkflowAdapter awfAdapter,
-            final UserWorkflowAdapter uwfAdapter,
-            final GroupWorkflowAdapter gwfAdapter,
-            final AnyObjectProvisioningManager aProvisioningManager,
-            final UserProvisioningManager uProvisioningManager,
-            final GroupProvisioningManager gProvisioningManager,
-            final VirAttrCache virAttrCache,
-            final NotificationManager notificationManager,
-            final AuditManager auditManager,
-            final PasswordGenerator passwordGenerator,
-            final EntityFactory entityFactory,
-            final PlainSchemaDAO plainSchemaDAO,
-            final PlainAttrDAO plainAttrDAO,
-            final PlainAttrValueDAO plainAttrValueDAO,
-            final AnySearchDAO anySearchDAO,
-            final ImplementationLookup implLookup,
+            final RoleDAO roleDAO,
             final PolicyDAO policyDAO,
             final NotificationDAO notificationDAO,
             final TaskDAO taskDAO,
             final VirSchemaDAO virSchemaDAO,
-            final RoleDAO roleDAO,
             final SecurityQuestionDAO securityQuestionDAO,
-            @Qualifier("asyncConnectorFacadeExecutor")
-            final ThreadPoolTaskExecutor asyncConnectorFacadeExecutor,
-            @Qualifier("propagationTaskExecutorAsyncExecutor")
-            final ThreadPoolTaskExecutor propagationTaskExecutorAsyncExecutor) {
+            final ConfParamOps confParamOps,
+            final ConnIdBundleManager bundleManager,
+            final ImplementationLookup implLookup,
+            final ApplicationContext ctx) {
 
-        return new DefaultSyncopeCoreInfoContributor(securityProperties,
-                persistenceProperties,
-                provisioningProperties,
-                logicProperties,
+        return new DefaultSyncopeCoreInfoContributor(
                 anyTypeDAO,
                 anyTypeClassDAO,
+                resourceDAO,
                 userDAO,
                 groupDAO,
                 anyObjectDAO,
-                resourceDAO,
-                confParamOps,
-                serviceOps,
-                bundleManager,
-                propagationTaskExecutor,
-                awfAdapter,
-                uwfAdapter,
-                gwfAdapter,
-                aProvisioningManager,
-                uProvisioningManager,
-                gProvisioningManager,
-                virAttrCache,
-                notificationManager,
-                auditManager,
-                passwordGenerator,
-                entityFactory,
-                plainSchemaDAO,
-                plainAttrDAO,
-                plainAttrValueDAO,
-                anySearchDAO,
-                implLookup,
+                roleDAO,
                 policyDAO,
                 notificationDAO,
                 taskDAO,
                 virSchemaDAO,
-                roleDAO,
                 securityQuestionDAO,
-                asyncConnectorFacadeExecutor,
-                propagationTaskExecutorAsyncExecutor);
+                confParamOps,
+                bundleManager,
+                implLookup,
+                ctx.getBeansOfType(ThreadPoolTaskExecutor.class));
     }
 
     @ConditionalOnMissingBean
diff --git a/core/starter/src/main/java/org/apache/syncope/core/starter/actuate/DefaultSyncopeCoreInfoContributor.java b/core/starter/src/main/java/org/apache/syncope/core/starter/actuate/DefaultSyncopeCoreInfoContributor.java
index 5c27b103e2..5b869e0183 100644
--- a/core/starter/src/main/java/org/apache/syncope/core/starter/actuate/DefaultSyncopeCoreInfoContributor.java
+++ b/core/starter/src/main/java/org/apache/syncope/core/starter/actuate/DefaultSyncopeCoreInfoContributor.java
@@ -31,7 +31,6 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
-import org.apache.syncope.common.keymaster.client.api.ServiceOps;
 import org.apache.syncope.common.lib.info.JavaImplInfo;
 import org.apache.syncope.common.lib.info.NumbersInfo;
 import org.apache.syncope.common.lib.info.PlatformInfo;
@@ -39,18 +38,13 @@ import org.apache.syncope.common.lib.info.SystemInfo;
 import org.apache.syncope.common.lib.types.EntitlementsHolder;
 import org.apache.syncope.common.lib.types.ImplementationTypesHolder;
 import org.apache.syncope.common.lib.types.TaskType;
-import org.apache.syncope.core.logic.LogicProperties;
 import org.apache.syncope.core.persistence.api.ImplementationLookup;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
-import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.NotificationDAO;
-import org.apache.syncope.core.persistence.api.dao.PlainAttrDAO;
-import org.apache.syncope.core.persistence.api.dao.PlainAttrValueDAO;
-import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.PolicyDAO;
 import org.apache.syncope.core.persistence.api.dao.RoleDAO;
 import org.apache.syncope.core.persistence.api.dao.SecurityQuestionDAO;
@@ -59,29 +53,13 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
-import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
-import org.apache.syncope.core.persistence.jpa.PersistenceProperties;
-import org.apache.syncope.core.provisioning.api.AnyObjectProvisioningManager;
-import org.apache.syncope.core.provisioning.api.AuditManager;
 import org.apache.syncope.core.provisioning.api.ConnIdBundleManager;
-import org.apache.syncope.core.provisioning.api.GroupProvisioningManager;
-import org.apache.syncope.core.provisioning.api.UserProvisioningManager;
-import org.apache.syncope.core.provisioning.api.cache.VirAttrCache;
-import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
-import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
-import org.apache.syncope.core.provisioning.java.ProvisioningProperties;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
-import org.apache.syncope.core.spring.security.PasswordGenerator;
-import org.apache.syncope.core.spring.security.SecurityProperties;
-import org.apache.syncope.core.workflow.api.AnyObjectWorkflowAdapter;
-import org.apache.syncope.core.workflow.api.GroupWorkflowAdapter;
-import org.apache.syncope.core.workflow.api.UserWorkflowAdapter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.aop.support.AopUtils;
 import org.springframework.boot.actuate.info.Info;
 import org.springframework.boot.actuate.info.InfoContributor;
 import org.springframework.context.PayloadApplicationEvent;
@@ -105,30 +83,34 @@ public class DefaultSyncopeCoreInfoContributor implements SyncopeCoreInfoContrib
             + "queued tasks = ([0-9]+), "
             + "completed tasks = ([0-9]+).*");
 
-    protected static void setTaskExecutorInfo(final String toString, final NumbersInfo.TaskExecutorInfo info) {
+    protected static NumbersInfo.TaskExecutorInfo getTaskExecutorInfo(final String toString) {
+        NumbersInfo.TaskExecutorInfo info = new NumbersInfo.TaskExecutorInfo();
+
         Matcher matcher = THREADPOOLTASKEXECUTOR_PATTERN.matcher(toString);
         if (matcher.matches() && matcher.groupCount() == 4) {
             try {
-                info.setSize(Integer.valueOf(matcher.group(1)));
+                info.setSize(Integer.parseInt(matcher.group(1)));
             } catch (NumberFormatException e) {
                 LOG.error("While parsing thread pool size", e);
             }
             try {
-                info.setActive(Integer.valueOf(matcher.group(2)));
+                info.setActive(Integer.parseInt(matcher.group(2)));
             } catch (NumberFormatException e) {
                 LOG.error("While parsing active threads #", e);
             }
             try {
-                info.setQueued(Integer.valueOf(matcher.group(3)));
+                info.setQueued(Integer.parseInt(matcher.group(3)));
             } catch (NumberFormatException e) {
                 LOG.error("While parsing queued threads #", e);
             }
             try {
-                info.setCompleted(Integer.valueOf(matcher.group(4)));
+                info.setCompleted(Integer.parseInt(matcher.group(4)));
             } catch (NumberFormatException e) {
                 LOG.error("While parsing completed threads #", e);
             }
         }
+
+        return info;
     }
 
     protected static void initSystemInfo() {
@@ -155,160 +137,72 @@ public class DefaultSyncopeCoreInfoContributor implements SyncopeCoreInfoContrib
         }
     }
 
-    private final SecurityProperties securityProperties;
-
-    private final PersistenceProperties persistenceProperties;
-
-    private final ProvisioningProperties provisioningProperties;
-
-    private final LogicProperties logicProperties;
-
-    private final AnyTypeDAO anyTypeDAO;
-
-    private final AnyTypeClassDAO anyTypeClassDAO;
-
-    private final UserDAO userDAO;
-
-    private final GroupDAO groupDAO;
-
-    private final AnyObjectDAO anyObjectDAO;
-
-    private final ExternalResourceDAO resourceDAO;
-
-    private final ConfParamOps confParamOps;
-
-    private final ServiceOps serviceOps;
-
-    private final ConnIdBundleManager bundleManager;
-
-    private final PropagationTaskExecutor propagationTaskExecutor;
-
-    private final AnyObjectWorkflowAdapter awfAdapter;
-
-    private final UserWorkflowAdapter uwfAdapter;
-
-    private final GroupWorkflowAdapter gwfAdapter;
-
-    private final AnyObjectProvisioningManager aProvisioningManager;
-
-    private final UserProvisioningManager uProvisioningManager;
-
-    private final GroupProvisioningManager gProvisioningManager;
-
-    private final VirAttrCache virAttrCache;
-
-    private final NotificationManager notificationManager;
+    protected final AnyTypeDAO anyTypeDAO;
 
-    private final AuditManager auditManager;
+    protected final AnyTypeClassDAO anyTypeClassDAO;
 
-    private final PasswordGenerator passwordGenerator;
+    protected final ExternalResourceDAO resourceDAO;
 
-    private final EntityFactory entityFactory;
+    protected final UserDAO userDAO;
 
-    private final PlainSchemaDAO plainSchemaDAO;
+    protected final GroupDAO groupDAO;
 
-    private final PlainAttrDAO plainAttrDAO;
+    protected final AnyObjectDAO anyObjectDAO;
 
-    private final PlainAttrValueDAO plainAttrValueDAO;
+    protected final RoleDAO roleDAO;
 
-    private final AnySearchDAO anySearchDAO;
+    protected final PolicyDAO policyDAO;
 
-    private final ImplementationLookup implLookup;
+    protected final TaskDAO taskDAO;
 
-    private final PolicyDAO policyDAO;
+    protected final VirSchemaDAO virSchemaDAO;
 
-    private final NotificationDAO notificationDAO;
+    protected final SecurityQuestionDAO securityQuestionDAO;
 
-    private final TaskDAO taskDAO;
+    protected final NotificationDAO notificationDAO;
 
-    private final VirSchemaDAO virSchemaDAO;
+    protected final ConfParamOps confParamOps;
 
-    private final RoleDAO roleDAO;
+    protected final ConnIdBundleManager bundleManager;
 
-    private final SecurityQuestionDAO securityQuestionDAO;
+    protected final ImplementationLookup implLookup;
 
-    private final ThreadPoolTaskExecutor asyncConnectorFacadeExecutor;
-
-    private final ThreadPoolTaskExecutor propagationTaskExecutorAsyncExecutor;
+    protected final Map<String, ThreadPoolTaskExecutor> taskExecutors;
 
     public DefaultSyncopeCoreInfoContributor(
-            final SecurityProperties securityProperties,
-            final PersistenceProperties persistenceProperties,
-            final ProvisioningProperties provisioningProperties,
-            final LogicProperties logicProperties,
             final AnyTypeDAO anyTypeDAO,
             final AnyTypeClassDAO anyTypeClassDAO,
+            final ExternalResourceDAO resourceDAO,
             final UserDAO userDAO,
             final GroupDAO groupDAO,
             final AnyObjectDAO anyObjectDAO,
-            final ExternalResourceDAO resourceDAO,
-            final ConfParamOps confParamOps,
-            final ServiceOps serviceOps,
-            final ConnIdBundleManager bundleManager,
-            final PropagationTaskExecutor propagationTaskExecutor,
-            final AnyObjectWorkflowAdapter awfAdapter,
-            final UserWorkflowAdapter uwfAdapter,
-            final GroupWorkflowAdapter gwfAdapter,
-            final AnyObjectProvisioningManager aProvisioningManager,
-            final UserProvisioningManager uProvisioningManager,
-            final GroupProvisioningManager gProvisioningManager,
-            final VirAttrCache virAttrCache,
-            final NotificationManager notificationManager,
-            final AuditManager auditManager,
-            final PasswordGenerator passwordGenerator,
-            final EntityFactory entityFactory,
-            final PlainSchemaDAO plainSchemaDAO,
-            final PlainAttrDAO plainAttrDAO,
-            final PlainAttrValueDAO plainAttrValueDAO,
-            final AnySearchDAO anySearchDAO,
-            final ImplementationLookup implLookup,
+            final RoleDAO roleDAO,
             final PolicyDAO policyDAO,
             final NotificationDAO notificationDAO,
             final TaskDAO taskDAO,
             final VirSchemaDAO virSchemaDAO,
-            final RoleDAO roleDAO,
             final SecurityQuestionDAO securityQuestionDAO,
-            final ThreadPoolTaskExecutor asyncConnectorFacadeExecutor,
-            final ThreadPoolTaskExecutor propagationTaskExecutorAsyncExecutor) {
+            final ConfParamOps confParamOps,
+            final ConnIdBundleManager bundleManager,
+            final ImplementationLookup implLookup,
+            final Map<String, ThreadPoolTaskExecutor> taskExecutors) {
 
-        this.securityProperties = securityProperties;
-        this.persistenceProperties = persistenceProperties;
-        this.provisioningProperties = provisioningProperties;
-        this.logicProperties = logicProperties;
         this.anyTypeDAO = anyTypeDAO;
         this.anyTypeClassDAO = anyTypeClassDAO;
+        this.resourceDAO = resourceDAO;
         this.userDAO = userDAO;
         this.groupDAO = groupDAO;
         this.anyObjectDAO = anyObjectDAO;
-        this.resourceDAO = resourceDAO;
-        this.confParamOps = confParamOps;
-        this.serviceOps = serviceOps;
-        this.bundleManager = bundleManager;
-        this.propagationTaskExecutor = propagationTaskExecutor;
-        this.awfAdapter = awfAdapter;
-        this.uwfAdapter = uwfAdapter;
-        this.gwfAdapter = gwfAdapter;
-        this.aProvisioningManager = aProvisioningManager;
-        this.uProvisioningManager = uProvisioningManager;
-        this.gProvisioningManager = gProvisioningManager;
-        this.virAttrCache = virAttrCache;
-        this.notificationManager = notificationManager;
-        this.auditManager = auditManager;
-        this.passwordGenerator = passwordGenerator;
-        this.entityFactory = entityFactory;
-        this.plainSchemaDAO = plainSchemaDAO;
-        this.plainAttrDAO = plainAttrDAO;
-        this.plainAttrValueDAO = plainAttrValueDAO;
-        this.anySearchDAO = anySearchDAO;
-        this.implLookup = implLookup;
+        this.roleDAO = roleDAO;
         this.policyDAO = policyDAO;
         this.notificationDAO = notificationDAO;
         this.taskDAO = taskDAO;
         this.virSchemaDAO = virSchemaDAO;
-        this.roleDAO = roleDAO;
         this.securityQuestionDAO = securityQuestionDAO;
-        this.asyncConnectorFacadeExecutor = asyncConnectorFacadeExecutor;
-        this.propagationTaskExecutorAsyncExecutor = propagationTaskExecutorAsyncExecutor;
+        this.confParamOps = confParamOps;
+        this.bundleManager = bundleManager;
+        this.implLookup = implLookup;
+        this.taskExecutors = taskExecutors;
     }
 
     protected boolean isSelfRegAllowed() {
@@ -327,53 +221,10 @@ public class DefaultSyncopeCoreInfoContributor implements SyncopeCoreInfoContrib
         synchronized (this) {
             if (PLATFORM_INFO == null) {
                 PLATFORM_INFO = new PlatformInfo();
-                PLATFORM_INFO.setKeymasterConfParamOps(AopUtils.getTargetClass(confParamOps).getName());
-                PLATFORM_INFO.setKeymasterServiceOps(AopUtils.getTargetClass(serviceOps).getName());
 
                 PLATFORM_INFO.getConnIdLocations().addAll(bundleManager.getLocations().stream().
                         map(URI::toASCIIString).collect(Collectors.toList()));
 
-                PLATFORM_INFO.getWorkflowInfo().
-                        setAnyObjectWorkflowAdapter(AopUtils.getTargetClass(awfAdapter).getName());
-                PLATFORM_INFO.getWorkflowInfo().
-                        setUserWorkflowAdapter(AopUtils.getTargetClass(uwfAdapter).getName());
-                PLATFORM_INFO.getWorkflowInfo().
-                        setGroupWorkflowAdapter(AopUtils.getTargetClass(gwfAdapter).getName());
-
-                PLATFORM_INFO.getProvisioningInfo().
-                        setAnyObjectProvisioningManager(AopUtils.getTargetClass(aProvisioningManager).getName());
-                PLATFORM_INFO.getProvisioningInfo().
-                        setUserProvisioningManager(AopUtils.getTargetClass(uProvisioningManager).getName());
-                PLATFORM_INFO.getProvisioningInfo().
-                        setGroupProvisioningManager(AopUtils.getTargetClass(gProvisioningManager).getName());
-                PLATFORM_INFO.getProvisioningInfo().
-                        setPropagationTaskExecutor(AopUtils.getTargetClass(propagationTaskExecutor).getName());
-                PLATFORM_INFO.getProvisioningInfo().
-                        setVirAttrCache(AopUtils.getTargetClass(virAttrCache).getName());
-                PLATFORM_INFO.getProvisioningInfo().
-                        setNotificationManager(AopUtils.getTargetClass(notificationManager).getName());
-                PLATFORM_INFO.getProvisioningInfo().
-                        setAuditManager(AopUtils.getTargetClass(auditManager).getName());
-
-                PLATFORM_INFO.setPasswordGenerator(AopUtils.getTargetClass(passwordGenerator).getName());
-
-                PLATFORM_INFO.getPersistenceInfo().
-                        setEntityFactory(AopUtils.getTargetClass(entityFactory).getName());
-                PLATFORM_INFO.getPersistenceInfo().
-                        setPlainSchemaDAO(AopUtils.getTargetClass(plainSchemaDAO).getName());
-                PLATFORM_INFO.getPersistenceInfo().
-                        setPlainAttrDAO(AopUtils.getTargetClass(plainAttrDAO).getName());
-                PLATFORM_INFO.getPersistenceInfo().
-                        setPlainAttrValueDAO(AopUtils.getTargetClass(plainAttrValueDAO).getName());
-                PLATFORM_INFO.getPersistenceInfo().
-                        setAnySearchDAO(AopUtils.getTargetClass(anySearchDAO).getName());
-                PLATFORM_INFO.getPersistenceInfo().
-                        setUserDAO(AopUtils.getTargetClass(userDAO).getName());
-                PLATFORM_INFO.getPersistenceInfo().
-                        setGroupDAO(AopUtils.getTargetClass(groupDAO).getName());
-                PLATFORM_INFO.getPersistenceInfo().
-                        setAnyObjectDAO(AopUtils.getTargetClass(anyObjectDAO).getName());
-
                 ImplementationTypesHolder.getInstance().getValues().forEach((typeName, typeInterface) -> {
                     Set<String> classNames = implLookup.getClassNames(typeName);
                     if (classNames != null) {
@@ -467,12 +318,8 @@ public class DefaultSyncopeCoreInfoContributor implements SyncopeCoreInfoContrib
         numbersInfo.getConfCompleteness().put(
                 NumbersInfo.ConfItem.ROLE.name(), numbersInfo.getTotalRoles() > 0);
 
-        setTaskExecutorInfo(
-                asyncConnectorFacadeExecutor.getThreadPoolExecutor().toString(),
-                numbersInfo.getAsyncConnectorExecutor());
-        setTaskExecutorInfo(
-                propagationTaskExecutorAsyncExecutor.getThreadPoolExecutor().toString(),
-                numbersInfo.getPropagationTaskExecutor());
+        taskExecutors.forEach((name, bean) -> numbersInfo.getTaskExecutorInfos().
+                put(name, getTaskExecutorInfo(bean.getThreadPoolExecutor().toString())));
 
         return numbersInfo;
     }
@@ -494,11 +341,6 @@ public class DefaultSyncopeCoreInfoContributor implements SyncopeCoreInfoContrib
 
         buildSystem();
         builder.withDetail("system", SYSTEM_INFO);
-
-        builder.withDetail("securityProperties", securityProperties);
-        builder.withDetail("persistenceProperties", persistenceProperties);
-        builder.withDetail("provisioningProperties", provisioningProperties);
-        builder.withDetail("logicProperties", logicProperties);
     }
 
     @Override
diff --git a/core/starter/src/main/resources/core.properties b/core/starter/src/main/resources/core.properties
index bee222e60d..19b4ffaa7c 100644
--- a/core/starter/src/main/resources/core.properties
+++ b/core/starter/src/main/resources/core.properties
@@ -109,12 +109,6 @@ security.digester.invertPositionOfPlainSaltInEncryptionResults=true
 security.digester.invertPositionOfSaltInMessageBeforeDigesting=true
 security.digester.useLenientSaltSizeCheck=true
 
-#########
-# Logic #
-#########
-
-logic.enableJDBCAuditAppender=true
-
 #########
 # Disable CGLib Proxies #
 #########
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 babd0d784e..1995c1bf82 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
@@ -25,18 +25,20 @@ import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Lazy;
 
+@EnableConfigurationProperties(ElasticsearchProperties.class)
 @Configuration(proxyBeanMethods = false)
 public class ElasticsearchClientContext {
 
     @ConditionalOnMissingBean
     @Bean
-    public ElasticsearchClientFactoryBean elasticsearchClientFactoryBean() {
+    public ElasticsearchClientFactoryBean elasticsearchClientFactoryBean(final ElasticsearchProperties props) {
         return new ElasticsearchClientFactoryBean(
-                List.of(new HttpHost("localhost", 9200, "http")));
+                List.of(new HttpHost(props.getHostname(), props.getPort(), props.getScheme())));
     }
 
     @ConditionalOnMissingBean
@@ -46,20 +48,26 @@ public class ElasticsearchClientContext {
             final @Lazy GroupDAO groupDAO,
             final @Lazy AnyObjectDAO anyObjectDAO) {
 
-        ElasticsearchUtils utils = new ElasticsearchUtils(userDAO, groupDAO, anyObjectDAO);
-        utils.setIndexMaxResultWindow(10000);
-        utils.setRetryOnConflict(5);
-        utils.setNumberOfShards(1);
-        utils.setNumberOfReplicas(1);
-        return utils;
+        return new ElasticsearchUtils(userDAO, groupDAO, anyObjectDAO);
     }
 
     @ConditionalOnMissingBean
     @Bean
     public ElasticsearchIndexManager elasticsearchIndexManager(
+            final ElasticsearchProperties props,
             final ElasticsearchClient client,
             final ElasticsearchUtils elasticsearchUtils) {
 
-        return new ElasticsearchIndexManager(client, elasticsearchUtils);
+        return new ElasticsearchIndexManager(
+                client,
+                elasticsearchUtils,
+                props.getNumberOfShards(),
+                props.getNumberOfReplicas());
+    }
+
+    @ConditionalOnMissingBean
+    @Bean
+    public ElasticsearchIndexLoader elasticsearchIndexLoader(final ElasticsearchIndexManager indexManager) {
+        return new ElasticsearchIndexLoader(indexManager);
     }
 }
diff --git a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/DomainIndexLoader.java b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexLoader.java
similarity index 61%
rename from ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/DomainIndexLoader.java
rename to ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexLoader.java
index b2595be40e..c3fe69d34d 100644
--- a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/DomainIndexLoader.java
+++ b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexLoader.java
@@ -16,23 +16,22 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.persistence.jpa;
+package org.apache.syncope.ext.elasticsearch.client;
 
 import javax.sql.DataSource;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.SyncopeCoreLoader;
-import org.apache.syncope.ext.elasticsearch.client.ElasticsearchIndexManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.core.Ordered;
 
-public class DomainIndexLoader implements SyncopeCoreLoader {
+public class ElasticsearchIndexLoader implements SyncopeCoreLoader {
 
-    protected static final Logger LOG = LoggerFactory.getLogger(DomainIndexLoader.class);
+    protected static final Logger LOG = LoggerFactory.getLogger(ElasticsearchIndexLoader.class);
 
     protected final ElasticsearchIndexManager indexManager;
 
-    public DomainIndexLoader(final ElasticsearchIndexManager indexManager) {
+    public ElasticsearchIndexLoader(final ElasticsearchIndexManager indexManager) {
         this.indexManager = indexManager;
     }
 
@@ -44,20 +43,25 @@ public class DomainIndexLoader implements SyncopeCoreLoader {
     @Override
     public void load(final String domain, final DataSource datasource) {
         try {
-            if (!indexManager.existsIndex(domain, AnyTypeKind.USER)) {
-                indexManager.createIndex(domain, AnyTypeKind.USER,
-                        indexManager.defaultSettings(), indexManager.defaultMapping());
+            if (!indexManager.existsAnyIndex(domain, AnyTypeKind.USER)) {
+                indexManager.createAnyIndex(domain, AnyTypeKind.USER,
+                        indexManager.defaultSettings(), indexManager.defaultAnyMapping());
             }
-            if (!indexManager.existsIndex(domain, AnyTypeKind.GROUP)) {
-                indexManager.createIndex(domain, AnyTypeKind.GROUP,
-                        indexManager.defaultSettings(), indexManager.defaultMapping());
+            if (!indexManager.existsAnyIndex(domain, AnyTypeKind.GROUP)) {
+                indexManager.createAnyIndex(domain, AnyTypeKind.GROUP,
+                        indexManager.defaultSettings(), indexManager.defaultAnyMapping());
             }
-            if (!indexManager.existsIndex(domain, AnyTypeKind.ANY_OBJECT)) {
-                indexManager.createIndex(domain, AnyTypeKind.ANY_OBJECT,
-                        indexManager.defaultSettings(), indexManager.defaultMapping());
+            if (!indexManager.existsAnyIndex(domain, AnyTypeKind.ANY_OBJECT)) {
+                indexManager.createAnyIndex(domain, AnyTypeKind.ANY_OBJECT,
+                        indexManager.defaultSettings(), indexManager.defaultAnyMapping());
+            }
+
+            if (!indexManager.existsAuditIndex(domain)) {
+                indexManager.createAuditIndex(domain,
+                        indexManager.defaultSettings(), indexManager.defaultAuditMapping());
             }
         } catch (Exception e) {
-            LOG.error("While creating index for domain {}", domain, e);
+            LOG.error("While creating indexes for domain {}", domain, e);
         }
     }
 }
diff --git a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexManager.java b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexManager.java
index 8822a2ab91..1a9bc7ad98 100644
--- a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexManager.java
+++ b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchIndexManager.java
@@ -24,7 +24,9 @@ import co.elastic.clients.elasticsearch._types.analysis.CustomNormalizer;
 import co.elastic.clients.elasticsearch._types.analysis.Normalizer;
 import co.elastic.clients.elasticsearch._types.mapping.DynamicTemplate;
 import co.elastic.clients.elasticsearch._types.mapping.KeywordProperty;
+import co.elastic.clients.elasticsearch._types.mapping.ObjectProperty;
 import co.elastic.clients.elasticsearch._types.mapping.Property;
+import co.elastic.clients.elasticsearch._types.mapping.TextProperty;
 import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
 import co.elastic.clients.elasticsearch.core.DeleteRequest;
 import co.elastic.clients.elasticsearch.core.DeleteResponse;
@@ -34,14 +36,17 @@ import co.elastic.clients.elasticsearch.indices.CreateIndexRequest;
 import co.elastic.clients.elasticsearch.indices.CreateIndexResponse;
 import co.elastic.clients.elasticsearch.indices.DeleteIndexRequest;
 import co.elastic.clients.elasticsearch.indices.DeleteIndexResponse;
+import co.elastic.clients.elasticsearch.indices.ExistsRequest;
 import co.elastic.clients.elasticsearch.indices.IndexSettings;
 import co.elastic.clients.elasticsearch.indices.IndexSettingsAnalysis;
+import com.fasterxml.jackson.databind.JsonNode;
 import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.provisioning.api.event.AnyLifecycleEvent;
+import org.apache.syncope.core.spring.security.SecureRandomUtils;
 import org.identityconnectors.framework.common.objects.SyncDeltaType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -58,18 +63,31 @@ public class ElasticsearchIndexManager {
 
     protected final ElasticsearchUtils elasticsearchUtils;
 
+    protected final String numberOfShards;
+
+    protected final String numberOfReplicas;
+
     public ElasticsearchIndexManager(
             final ElasticsearchClient client,
-            final ElasticsearchUtils elasticsearchUtils) {
+            final ElasticsearchUtils elasticsearchUtils,
+            final String numberOfShards,
+            final String numberOfReplicas) {
 
         this.client = client;
         this.elasticsearchUtils = elasticsearchUtils;
+        this.numberOfShards = numberOfShards;
+        this.numberOfReplicas = numberOfReplicas;
     }
 
-    public boolean existsIndex(final String domain, final AnyTypeKind kind) throws IOException {
-        return client.indices().exists(
-                new co.elastic.clients.elasticsearch.indices.ExistsRequest.Builder().
-                        index(ElasticsearchUtils.getContextDomainName(domain, kind)).build()).
+    public boolean existsAnyIndex(final String domain, final AnyTypeKind kind) throws IOException {
+        return client.indices().exists(new ExistsRequest.Builder().
+                index(ElasticsearchUtils.getAnyIndex(domain, kind)).build()).
+                value();
+    }
+
+    public boolean existsAuditIndex(final String domain) throws IOException {
+        return client.indices().exists(new ExistsRequest.Builder().
+                index(ElasticsearchUtils.getAuditIndex(domain)).build()).
                 value();
     }
 
@@ -83,12 +101,12 @@ public class ElasticsearchIndexManager {
                                         build()).
                                 build()).
                         build()).
-                numberOfShards(elasticsearchUtils.getNumberOfShards()).
-                numberOfReplicas(elasticsearchUtils.getNumberOfReplicas()).
+                numberOfShards(numberOfShards).
+                numberOfReplicas(numberOfReplicas).
                 build();
     }
 
-    public TypeMapping defaultMapping() throws IOException {
+    public TypeMapping defaultAnyMapping() throws IOException {
         return new TypeMapping.Builder().
                 dynamicTemplates(List.of(Map.of(
                         "strings",
@@ -101,7 +119,45 @@ public class ElasticsearchIndexManager {
                 build();
     }
 
-    protected CreateIndexResponse doCreateIndex(
+    public TypeMapping defaultAuditMapping() throws IOException {
+        return new TypeMapping.Builder().
+                dynamicTemplates(List.of(Map.of(
+                        "strings",
+                        new DynamicTemplate.Builder().
+                                matchMappingType("string").
+                                mapping(new Property.Builder().
+                                        keyword(new KeywordProperty.Builder().normalizer("string_lowercase").build()).
+                                        build()).
+                                build()))).
+                properties(
+                        "message",
+                        new Property.Builder().object(new ObjectProperty.Builder().
+                                properties(
+                                        "before",
+                                        new Property.Builder().
+                                                text(new TextProperty.Builder().analyzer("standard").build()).
+                                                build()).
+                                properties(
+                                        "inputs",
+                                        new Property.Builder().
+                                                text(new TextProperty.Builder().analyzer("standard").build()).
+                                                build()).
+                                properties(
+                                        "output",
+                                        new Property.Builder().
+                                                text(new TextProperty.Builder().analyzer("standard").build()).
+                                                build()).
+                                properties(
+                                        "throwable",
+                                        new Property.Builder().
+                                                text(new TextProperty.Builder().analyzer("standard").build()).
+                                                build()).
+                                build()).
+                                build()).
+                build();
+    }
+
+    protected CreateIndexResponse doCreateAnyIndex(
             final String domain,
             final AnyTypeKind kind,
             final IndexSettings settings,
@@ -109,13 +165,13 @@ public class ElasticsearchIndexManager {
 
         return client.indices().create(
                 new CreateIndexRequest.Builder().
-                        index(ElasticsearchUtils.getContextDomainName(domain, kind)).
+                        index(ElasticsearchUtils.getAnyIndex(domain, kind)).
                         settings(settings).
                         mappings(mappings).
                         build());
     }
 
-    public void createIndex(
+    public void createAnyIndex(
             final String domain,
             final AnyTypeKind kind,
             final IndexSettings settings,
@@ -123,44 +179,79 @@ public class ElasticsearchIndexManager {
             throws IOException {
 
         try {
-            CreateIndexResponse response = doCreateIndex(domain, kind, settings, mappings);
+            CreateIndexResponse response = doCreateAnyIndex(domain, kind, settings, mappings);
 
             LOG.debug("Successfully created {} for {}: {}",
-                    ElasticsearchUtils.getContextDomainName(domain, kind), kind.name(), response);
+                    ElasticsearchUtils.getAnyIndex(domain, kind), kind.name(), response);
         } catch (ElasticsearchException e) {
             LOG.debug("Could not create index {} because it already exists",
-                    ElasticsearchUtils.getContextDomainName(domain, kind), e);
+                    ElasticsearchUtils.getAnyIndex(domain, kind), e);
 
-            removeIndex(domain, kind);
-            doCreateIndex(domain, kind, settings, mappings);
+            removeAnyIndex(domain, kind);
+            doCreateAnyIndex(domain, kind, settings, mappings);
         }
     }
 
-    public void removeIndex(final String domain, final AnyTypeKind kind) throws IOException {
+    public void removeAnyIndex(final String domain, final AnyTypeKind kind) throws IOException {
         DeleteIndexResponse response = client.indices().delete(
-                new DeleteIndexRequest.Builder().index(ElasticsearchUtils.getContextDomainName(domain, kind)).build());
-        LOG.debug("Successfully removed {}: {}",
-                ElasticsearchUtils.getContextDomainName(domain, kind), response);
+                new DeleteIndexRequest.Builder().index(ElasticsearchUtils.getAnyIndex(domain, kind)).build());
+        LOG.debug("Successfully removed {}: {}", ElasticsearchUtils.getAnyIndex(domain, kind), response);
+    }
+
+    protected CreateIndexResponse doCreateAuditIndex(
+            final String domain,
+            final IndexSettings settings,
+            final TypeMapping mappings) throws IOException {
+
+        return client.indices().create(
+                new CreateIndexRequest.Builder().
+                        index(ElasticsearchUtils.getAuditIndex(domain)).
+                        settings(settings).
+                        mappings(mappings).
+                        build());
+    }
+
+    public void createAuditIndex(
+            final String domain,
+            final IndexSettings settings,
+            final TypeMapping mappings)
+            throws IOException {
+
+        try {
+            CreateIndexResponse response = doCreateAuditIndex(domain, settings, mappings);
+
+            LOG.debug("Successfully created audit index {}: {}",
+                    ElasticsearchUtils.getAuditIndex(domain), response);
+        } catch (ElasticsearchException e) {
+            LOG.debug("Could not create audit index {} because it already exists",
+                    ElasticsearchUtils.getAuditIndex(domain), e);
+
+            removeAuditIndex(domain);
+            doCreateAuditIndex(domain, settings, mappings);
+        }
+    }
+
+    public void removeAuditIndex(final String domain) throws IOException {
+        DeleteIndexResponse response = client.indices().delete(
+                new DeleteIndexRequest.Builder().index(ElasticsearchUtils.getAuditIndex(domain)).build());
+        LOG.debug("Successfully removed {}: {}", ElasticsearchUtils.getAuditIndex(domain), response);
     }
 
     @TransactionalEventListener
-    public void after(final AnyLifecycleEvent<Any<?>> event) throws IOException {
+    public void any(final AnyLifecycleEvent<Any<?>> event) throws IOException {
         LOG.debug("About to {} index for {}", event.getType().name(), event.getAny());
 
         if (event.getType() == SyncDeltaType.DELETE) {
             DeleteRequest request = new DeleteRequest.Builder().index(
-                    ElasticsearchUtils.getContextDomainName(event.getDomain(), event.getAny().getType().getKind())).
+                    ElasticsearchUtils.getAnyIndex(event.getDomain(), event.getAny().getType().getKind())).
                     id(event.getAny().getKey()).
                     build();
             DeleteResponse response = client.delete(request);
             LOG.debug("Index successfully deleted for {}[{}]: {}",
                     event.getAny().getType().getKind(), event.getAny().getKey(), response);
         } else {
-            String index = ElasticsearchUtils.getContextDomainName(
-                    event.getDomain(), event.getAny().getType().getKind());
-
             IndexRequest<Map<String, Object>> request = new IndexRequest.Builder<Map<String, Object>>().
-                    index(index).
+                    index(ElasticsearchUtils.getAnyIndex(event.getDomain(), event.getAny().getType().getKind())).
                     id(event.getAny().getKey()).
                     document(elasticsearchUtils.document(event.getAny(), event.getDomain())).
                     build();
@@ -168,4 +259,19 @@ public class ElasticsearchIndexManager {
             LOG.debug("Index successfully created or updated for {}: {}", event.getAny(), response);
         }
     }
+
+    public void audit(final String domain, final long instant, final JsonNode message)
+            throws IOException {
+
+        LOG.debug("About to audit");
+
+        IndexRequest<Map<String, Object>> request = new IndexRequest.Builder<Map<String, Object>>().
+                index(ElasticsearchUtils.getAuditIndex(domain)).
+                id(SecureRandomUtils.generateRandomUUID().toString()).
+                document(elasticsearchUtils.document(instant, message, domain)).
+                build();
+        IndexResponse response = client.index(request);
+
+        LOG.debug("Audit successfully created: {}", response);
+    }
 }
diff --git a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchProperties.java b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchProperties.java
new file mode 100644
index 0000000000..4a86dca700
--- /dev/null
+++ b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchProperties.java
@@ -0,0 +1,86 @@
+/*
+ * 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.ext.elasticsearch.client;
+
+import org.apache.http.HttpHost;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties("elasticsearch")
+public class ElasticsearchProperties {
+
+    private String hostname = "localhost";
+
+    private int port = 9200;
+
+    private String scheme = HttpHost.DEFAULT_SCHEME_NAME;
+
+    private int indexMaxResultWindow = 10000;
+
+    private String numberOfShards = "1";
+
+    private String numberOfReplicas = "1";
+
+    public String getHostname() {
+        return hostname;
+    }
+
+    public void setHostname(final String hostname) {
+        this.hostname = hostname;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public void setPort(final int port) {
+        this.port = port;
+    }
+
+    public String getScheme() {
+        return scheme;
+    }
+
+    public void setScheme(final String scheme) {
+        this.scheme = scheme;
+    }
+
+    public int getIndexMaxResultWindow() {
+        return indexMaxResultWindow;
+    }
+
+    public void setIndexMaxResultWindow(final int indexMaxResultWindow) {
+        this.indexMaxResultWindow = indexMaxResultWindow;
+    }
+
+    public String getNumberOfShards() {
+        return numberOfShards;
+    }
+
+    public void setNumberOfShards(final String numberOfShards) {
+        this.numberOfShards = numberOfShards;
+    }
+
+    public String getNumberOfReplicas() {
+        return numberOfReplicas;
+    }
+
+    public void setNumberOfReplicas(final String numberOfReplicas) {
+        this.numberOfReplicas = numberOfReplicas;
+    }
+}
diff --git a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
index a95d09162c..7421d0fc26 100644
--- a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
+++ b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.ext.elasticsearch.client;
 
+import com.fasterxml.jackson.databind.JsonNode;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -47,62 +48,30 @@ import org.springframework.transaction.annotation.Transactional;
  */
 public class ElasticsearchUtils {
 
-    public static String getContextDomainName(final String domain, final AnyTypeKind kind) {
+    public static String getAnyIndex(final String domain, final AnyTypeKind kind) {
         return domain.toLowerCase() + '_' + kind.name().toLowerCase();
     }
 
+    public static String getAuditIndex(final String domain) {
+        return domain.toLowerCase() + "_audit";
+    }
+
     protected final UserDAO userDAO;
 
     protected final GroupDAO groupDAO;
 
     protected final AnyObjectDAO anyObjectDAO;
 
-    protected int indexMaxResultWindow = 10000;
-
-    protected int retryOnConflict = 5;
-
-    protected String numberOfShards = "1";
-
-    protected String numberOfReplicas = "1";
+    public ElasticsearchUtils(
+            final UserDAO userDAO,
+            final GroupDAO groupDAO,
+            final AnyObjectDAO anyObjectDAO) {
 
-    public ElasticsearchUtils(final UserDAO userDAO, final GroupDAO groupDAO, final AnyObjectDAO anyObjectDAO) {
         this.userDAO = userDAO;
         this.groupDAO = groupDAO;
         this.anyObjectDAO = anyObjectDAO;
     }
 
-    public void setIndexMaxResultWindow(final int indexMaxResultWindow) {
-        this.indexMaxResultWindow = indexMaxResultWindow;
-    }
-
-    public int getIndexMaxResultWindow() {
-        return indexMaxResultWindow;
-    }
-
-    public void setRetryOnConflict(final int retryOnConflict) {
-        this.retryOnConflict = retryOnConflict;
-    }
-
-    public int getRetryOnConflict() {
-        return retryOnConflict;
-    }
-
-    public String getNumberOfShards() {
-        return numberOfShards;
-    }
-
-    public void setNumberOfShards(final int numberOfShards) {
-        this.numberOfShards = String.valueOf(numberOfShards);
-    }
-
-    public String getNumberOfReplicas() {
-        return numberOfReplicas;
-    }
-
-    public void setNumberOfReplicas(final int numberOfReplicas) {
-        this.numberOfReplicas = String.valueOf(numberOfReplicas);
-    }
-
     /**
      * Returns the document specialized with content from the provided any.
      *
@@ -254,4 +223,27 @@ public class ElasticsearchUtils {
     protected void customizeDocument(final Map<String, Object> builder, final User user, final String domain)
             throws IOException {
     }
+
+    public Map<String, Object> document(
+            final long instant,
+            final JsonNode message,
+            final String domain) throws IOException {
+
+        Map<String, Object> builder = new HashMap<>();
+
+        builder.put("instant", instant);
+        builder.put("message", message);
+
+        customizeDocument(builder, instant, message, domain);
+
+        return builder;
+    }
+
+    protected void customizeDocument(
+            final Map<String, Object> builder,
+            final long instant,
+            final JsonNode message,
+            final String domain)
+            throws IOException {
+    }
 }
diff --git a/ext/elasticsearch/pom.xml b/ext/elasticsearch/logic/pom.xml
similarity index 52%
copy from ext/elasticsearch/pom.xml
copy to ext/elasticsearch/logic/pom.xml
index 649af13516..50b986fe93 100644
--- a/ext/elasticsearch/pom.xml
+++ b/ext/elasticsearch/logic/pom.xml
@@ -22,25 +22,41 @@ under the License.
   <modelVersion>4.0.0</modelVersion>
 
   <parent>
-    <groupId>org.apache.syncope</groupId>
-    <artifactId>syncope-ext</artifactId>
+    <groupId>org.apache.syncope.ext</groupId>
+    <artifactId>syncope-ext-elasticsearch</artifactId>
     <version>3.0.0-SNAPSHOT</version>
   </parent>
 
-  <name>Apache Syncope Ext: Elasticsearch</name>
-  <description>Apache Syncope Ext: Elasticsearch</description>
-  <groupId>org.apache.syncope.ext</groupId>
-  <artifactId>syncope-ext-elasticsearch</artifactId>
-  <packaging>pom</packaging>
+  <name>Apache Syncope Ext: Elasticsearch Logic</name>
+  <description>Apache Syncope Ext: Elasticsearch Logic</description>
+  <groupId>org.apache.syncope.ext.elasticsearch</groupId>
+  <artifactId>syncope-ext-elasticsearch-logic</artifactId>
+  <packaging>jar</packaging>
   
   <properties>
-    <rootpom.basedir>${basedir}/../..</rootpom.basedir>
+    <rootpom.basedir>${basedir}/../../..</rootpom.basedir>
   </properties>
-  
-  <modules>
-    <module>client-elasticsearch</module>
-    <module>persistence-jpa</module>
-    <module>provisioning-java</module>
-  </modules>
 
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.syncope.core.idrepo</groupId>
+      <artifactId>syncope-core-idrepo-logic</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.syncope.ext.elasticsearch</groupId>
+      <artifactId>syncope-ext-elasticsearch-client</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
 </project>
diff --git a/ext/elasticsearch/logic/src/main/java/org/apache/syncope/core/logic/audit/ElasticsearchAppender.java b/ext/elasticsearch/logic/src/main/java/org/apache/syncope/core/logic/audit/ElasticsearchAppender.java
new file mode 100644
index 0000000000..5787e220ac
--- /dev/null
+++ b/ext/elasticsearch/logic/src/main/java/org/apache/syncope/core/logic/audit/ElasticsearchAppender.java
@@ -0,0 +1,97 @@
+/*
+ * 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.core.logic.audit;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import java.io.Serializable;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+import org.apache.syncope.ext.elasticsearch.client.ElasticsearchIndexManager;
+
+public class ElasticsearchAppender extends AbstractAppender {
+
+    public static class Builder extends AbstractAppender.Builder<Builder>
+            implements org.apache.logging.log4j.core.util.Builder<ElasticsearchAppender> {
+
+        private ElasticsearchIndexManager elasticsearchIndexManager;
+
+        private String domain;
+
+        public ElasticsearchAppender.Builder setDomain(final String domain) {
+            this.domain = domain;
+            return this;
+        }
+
+        public ElasticsearchAppender.Builder setIndexManager(
+                final ElasticsearchIndexManager elasticsearchIndexManager) {
+
+            this.elasticsearchIndexManager = elasticsearchIndexManager;
+            return this;
+        }
+
+        @Override
+        public ElasticsearchAppender build() {
+            if (domain == null || elasticsearchIndexManager == null) {
+                LOGGER.error("Cannot create ElasticsearchAppender without Domain or IndexManager.");
+                return null;
+            }
+            return new ElasticsearchAppender(
+                    getName(), getFilter(), getLayout(), isIgnoreExceptions(), domain, elasticsearchIndexManager);
+        }
+    }
+
+    @PluginBuilderFactory
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    private final String domain;
+
+    protected final ElasticsearchIndexManager elasticsearchIndexManager;
+
+    protected ElasticsearchAppender(
+            final String name,
+            final Filter filter,
+            final Layout<? extends Serializable> layout,
+            final boolean ignoreExceptions,
+            final String domain,
+            final ElasticsearchIndexManager elasticsearchIndexManager) {
+
+        super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY);
+        this.domain = domain;
+        this.elasticsearchIndexManager = elasticsearchIndexManager;
+    }
+
+    @Override
+    public void append(final LogEvent event) {
+        try {
+            elasticsearchIndexManager.audit(
+                    domain,
+                    event.getTimeMillis(),
+                    POJOHelper.deserialize(event.getMessage().getFormattedMessage(), JsonNode.class));
+        } catch (Exception e) {
+            LOGGER.error("While requesting to index event for appender [{}]", getName(), e);
+        }
+    }
+}
diff --git a/ext/elasticsearch/logic/src/main/java/org/apache/syncope/core/logic/audit/ElasticsearchAuditAppender.java b/ext/elasticsearch/logic/src/main/java/org/apache/syncope/core/logic/audit/ElasticsearchAuditAppender.java
new file mode 100644
index 0000000000..33ad815f14
--- /dev/null
+++ b/ext/elasticsearch/logic/src/main/java/org/apache/syncope/core/logic/audit/ElasticsearchAuditAppender.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic.audit;
+
+import java.util.Optional;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.syncope.ext.elasticsearch.client.ElasticsearchIndexManager;
+
+public class ElasticsearchAuditAppender extends DefaultAuditAppender {
+
+    public ElasticsearchAuditAppender(final String domain, final ElasticsearchIndexManager elasticsearchIndexManager) {
+        super(domain);
+
+        LoggerContext logCtx = (LoggerContext) LogManager.getContext(false);
+
+        targetAppender = Optional.ofNullable(logCtx.getConfiguration().<Appender>getAppender(getTargetAppenderName())).
+                orElseGet(() -> {
+                    ElasticsearchAppender a = ElasticsearchAppender.newBuilder().
+                            setName(getTargetAppenderName()).
+                            setIgnoreExceptions(false).
+                            setDomain(domain).
+                            setIndexManager(elasticsearchIndexManager).
+                            build();
+                    a.start();
+                    logCtx.getConfiguration().addAppender(a);
+                    return a;
+                });
+    }
+
+    @Override
+    public String getTargetAppenderName() {
+        return "audit_for_" + domain;
+    }
+}
diff --git a/ext/elasticsearch/logic/src/main/java/org/apache/syncope/core/logic/audit/ElasticsearchLogicContext.java b/ext/elasticsearch/logic/src/main/java/org/apache/syncope/core/logic/audit/ElasticsearchLogicContext.java
new file mode 100644
index 0000000000..09df992c1a
--- /dev/null
+++ b/ext/elasticsearch/logic/src/main/java/org/apache/syncope/core/logic/audit/ElasticsearchLogicContext.java
@@ -0,0 +1,62 @@
+/*
+ * 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.core.logic.audit;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+import org.apache.syncope.core.logic.IdRepoLogicContext;
+import org.apache.syncope.core.persistence.api.DomainHolder;
+import org.apache.syncope.ext.elasticsearch.client.ElasticsearchIndexManager;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@AutoConfigureBefore(IdRepoLogicContext.class)
+@Configuration(proxyBeanMethods = false)
+public class ElasticsearchLogicContext {
+
+    @ConditionalOnMissingBean(name = { "defaultAuditAppenders", "elasticsearchDefaultAuditAppenders" })
+    @Bean
+    public List<AuditAppender> defaultAuditAppenders(
+            final DomainHolder domainHolder,
+            final ElasticsearchIndexManager elasticsearchIndexManager) {
+
+        List<AuditAppender> auditAppenders = new ArrayList<>();
+
+        LoggerContext logCtx = (LoggerContext) LogManager.getContext(false);
+        domainHolder.getDomains().forEach((domain, dataSource) -> {
+            AuditAppender appender = new ElasticsearchAuditAppender(domain, elasticsearchIndexManager);
+
+            LoggerConfig logConf = new LoggerConfig(AuditLoggerName.getAuditLoggerName(domain), null, false);
+            logConf.addAppender(appender.getTargetAppender(), Level.DEBUG, null);
+            logConf.setLevel(Level.DEBUG);
+            logCtx.getConfiguration().addLogger(logConf.getName(), logConf);
+
+            auditAppenders.add(appender);
+        });
+
+        return auditAppenders;
+    }
+}
diff --git a/fit/core-reference/src/main/resources/core-elasticsearch.properties b/ext/elasticsearch/logic/src/main/resources/META-INF/spring.factories
similarity index 85%
copy from fit/core-reference/src/main/resources/core-elasticsearch.properties
copy to ext/elasticsearch/logic/src/main/resources/META-INF/spring.factories
index 3360988643..ca8f2cb4bd 100644
--- a/fit/core-reference/src/main/resources/core-elasticsearch.properties
+++ b/ext/elasticsearch/logic/src/main/resources/META-INF/spring.factories
@@ -15,4 +15,5 @@
 # specific language governing permissions and limitations
 # under the License.
 
-persistence.anySearchDao=org.apache.syncope.core.persistence.jpa.dao.ElasticsearchAnySearchDAO
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+  org.apache.syncope.core.logic.audit.ElasticsearchLogicContext
diff --git a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/ElasticsearchPersistenceContext.java b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/ElasticsearchPersistenceContext.java
index 757fb34cee..88e9e1e160 100644
--- a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/ElasticsearchPersistenceContext.java
+++ b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/ElasticsearchPersistenceContext.java
@@ -22,6 +22,7 @@ import co.elastic.clients.elasticsearch.ElasticsearchClient;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
+import org.apache.syncope.core.persistence.api.dao.AuditConfDAO;
 import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
@@ -30,8 +31,8 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.jpa.dao.ElasticsearchAnySearchDAO;
-import org.apache.syncope.ext.elasticsearch.client.ElasticsearchIndexManager;
-import org.apache.syncope.ext.elasticsearch.client.ElasticsearchUtils;
+import org.apache.syncope.core.persistence.jpa.dao.ElasticsearchAuditConfDAO;
+import org.apache.syncope.ext.elasticsearch.client.ElasticsearchProperties;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -40,15 +41,10 @@ import org.springframework.context.annotation.Lazy;
 @Configuration(proxyBeanMethods = false)
 public class ElasticsearchPersistenceContext {
 
-    @ConditionalOnMissingBean
-    @Bean
-    public DomainIndexLoader domainIndexLoader(final ElasticsearchIndexManager indexManager) {
-        return new DomainIndexLoader(indexManager);
-    }
-
     @ConditionalOnMissingBean(name = "elasticsearchAnySearchDAO")
     @Bean
     public AnySearchDAO anySearchDAO(
+            final ElasticsearchProperties props,
             final RealmDAO realmDAO,
             final @Lazy DynRealmDAO dynRealmDAO,
             final @Lazy UserDAO userDAO,
@@ -58,8 +54,7 @@ public class ElasticsearchPersistenceContext {
             final EntityFactory entityFactory,
             final AnyUtilsFactory anyUtilsFactory,
             final PlainAttrValidationManager validator,
-            final ElasticsearchClient client,
-            final @Lazy ElasticsearchUtils elasticsearchUtils) {
+            final ElasticsearchClient client) {
 
         return new ElasticsearchAnySearchDAO(
                 realmDAO,
@@ -72,6 +67,15 @@ public class ElasticsearchPersistenceContext {
                 anyUtilsFactory,
                 validator,
                 client,
-                elasticsearchUtils);
+                props.getIndexMaxResultWindow());
+    }
+
+    @ConditionalOnMissingBean(name = "elasticsearchAuditConfDAO")
+    @Bean
+    public AuditConfDAO auditConfDAO(
+            final ElasticsearchProperties props,
+            final ElasticsearchClient client) {
+
+        return new ElasticsearchAuditConfDAO(client, props.getIndexMaxResultWindow());
     }
 }
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 d70d75ef30..b7cb0c09c4 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
@@ -106,7 +106,7 @@ public class ElasticsearchAnySearchDAO extends AbstractAnySearchDAO {
 
     protected final ElasticsearchClient client;
 
-    protected final ElasticsearchUtils elasticsearchUtils;
+    protected final int indexMaxResultWindow;
 
     public ElasticsearchAnySearchDAO(
             final RealmDAO realmDAO,
@@ -119,7 +119,7 @@ public class ElasticsearchAnySearchDAO extends AbstractAnySearchDAO {
             final AnyUtilsFactory anyUtilsFactory,
             final PlainAttrValidationManager validator,
             final ElasticsearchClient client,
-            final ElasticsearchUtils elasticsearchUtils) {
+            final int indexMaxResultWindow) {
 
         super(
                 realmDAO,
@@ -133,7 +133,7 @@ public class ElasticsearchAnySearchDAO extends AbstractAnySearchDAO {
                 validator);
 
         this.client = client;
-        this.elasticsearchUtils = elasticsearchUtils;
+        this.indexMaxResultWindow = indexMaxResultWindow;
     }
 
     protected Triple<Optional<Query>, Set<String>, Set<String>> getAdminRealmsFilter(
@@ -237,7 +237,7 @@ public class ElasticsearchAnySearchDAO extends AbstractAnySearchDAO {
             final AnyTypeKind kind) {
 
         CountRequest request = new CountRequest.Builder().
-                index(ElasticsearchUtils.getContextDomainName(AuthContextUtils.getDomain(), kind)).
+                index(ElasticsearchUtils.getAnyIndex(AuthContextUtils.getDomain(), kind)).
                 query(getQuery(base, recursive, adminRealms, cond, kind)).
                 build();
         try {
@@ -298,11 +298,11 @@ public class ElasticsearchAnySearchDAO extends AbstractAnySearchDAO {
             final AnyTypeKind kind) {
 
         SearchRequest request = new SearchRequest.Builder().
-                index(ElasticsearchUtils.getContextDomainName(AuthContextUtils.getDomain(), kind)).
+                index(ElasticsearchUtils.getAnyIndex(AuthContextUtils.getDomain(), kind)).
                 searchType(SearchType.QueryThenFetch).
                 query(getQuery(base, recursive, adminRealms, cond, kind)).
                 from(itemsPerPage * (page <= 0 ? 0 : page - 1)).
-                size(itemsPerPage < 0 ? elasticsearchUtils.getIndexMaxResultWindow() : itemsPerPage).
+                size(itemsPerPage < 0 ? indexMaxResultWindow : itemsPerPage).
                 sort(sortBuilders(kind, orderBy)).
                 build();
 
diff --git a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAuditConfDAO.java b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAuditConfDAO.java
new file mode 100644
index 0000000000..4746205ae2
--- /dev/null
+++ b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAuditConfDAO.java
@@ -0,0 +1,182 @@
+/*
+ * 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.core.persistence.jpa.dao;
+
+import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import co.elastic.clients.elasticsearch._types.FieldSort;
+import co.elastic.clients.elasticsearch._types.SearchType;
+import co.elastic.clients.elasticsearch._types.SortOptions;
+import co.elastic.clients.elasticsearch._types.SortOrder;
+import co.elastic.clients.elasticsearch._types.query_dsl.Query;
+import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
+import co.elastic.clients.elasticsearch.core.CountRequest;
+import co.elastic.clients.elasticsearch.core.SearchRequest;
+import co.elastic.clients.elasticsearch.core.search.Hit;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.audit.AuditEntry;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.apache.syncope.ext.elasticsearch.client.ElasticsearchUtils;
+import org.springframework.util.CollectionUtils;
+
+public class ElasticsearchAuditConfDAO extends JPAAuditConfDAO {
+
+    protected final ElasticsearchClient client;
+
+    protected final int indexMaxResultWindow;
+
+    public ElasticsearchAuditConfDAO(final ElasticsearchClient client, final int indexMaxResultWindow) {
+        this.client = client;
+        this.indexMaxResultWindow = indexMaxResultWindow;
+    }
+
+    protected Query getQuery(
+            final String entityKey,
+            final AuditElements.EventCategoryType type,
+            final String category,
+            final String subcategory,
+            final List<String> events,
+            final AuditElements.Result result) {
+
+        List<Query> queries = new ArrayList<>();
+
+        if (entityKey != null) {
+            queries.add(new Query.Builder().
+                    multiMatch(QueryBuilders.multiMatch().
+                            fields("message.before", "message.inputs", "message.output", "message.throwable").
+                            query(entityKey).build()).build());
+        }
+
+        if (type != null) {
+            queries.add(new Query.Builder().
+                    term(QueryBuilders.term().field("message.logger.type").value(type.name()).build()).
+                    build());
+        }
+
+        if (StringUtils.isNotBlank(category)) {
+            queries.add(new Query.Builder().
+                    term(QueryBuilders.term().field("message.logger.category").value(category).build()).
+                    build());
+        }
+
+        if (StringUtils.isNotBlank(subcategory)) {
+            queries.add(new Query.Builder().
+                    term(QueryBuilders.term().field("message.logger.subcategory").value(subcategory).build()).
+                    build());
+        }
+
+        List<Query> eventQueries = events.stream().map(event -> new Query.Builder().
+                term(QueryBuilders.term().field("message.logger.event").value(event).build()).
+                build()).
+                collect(Collectors.toList());
+        if (!eventQueries.isEmpty()) {
+            queries.add(new Query.Builder().disMax(QueryBuilders.disMax().queries(eventQueries).build()).build());
+        }
+
+        if (result != null) {
+            queries.add(new Query.Builder().
+                    term(QueryBuilders.term().field("message.logger.result").value(result.name()).build()).
+                    build());
+        }
+
+        return new Query.Builder().bool(QueryBuilders.bool().must(queries).build()).build();
+    }
+
+    @Override
+    public int countEntries(
+            final String entityKey,
+            final AuditElements.EventCategoryType type,
+            final String category,
+            final String subcategory,
+            final List<String> events,
+            final AuditElements.Result result) {
+
+        CountRequest request = new CountRequest.Builder().
+                index(ElasticsearchUtils.getAuditIndex(AuthContextUtils.getDomain())).
+                query(getQuery(entityKey, type, category, subcategory, events, result)).
+                build();
+        try {
+            return (int) client.count(request).count();
+        } catch (IOException e) {
+            LOG.error("Search error", e);
+            return 0;
+        }
+    }
+
+    protected List<SortOptions> sortBuilders(final List<OrderByClause> orderBy) {
+        return orderBy.stream().map(clause -> {
+            String sortField = clause.getField();
+            if ("EVENT_DATE".equalsIgnoreCase(sortField)) {
+                sortField = "message.date";
+            }
+
+            return new SortOptions.Builder().field(
+                    new FieldSort.Builder().
+                            field(sortField).
+                            order(clause.getDirection() == OrderByClause.Direction.ASC
+                                    ? SortOrder.Asc : SortOrder.Desc).
+                            build()).
+                    build();
+        }).collect(Collectors.toList());
+    }
+
+    @Override
+    public List<AuditEntry> searchEntries(
+            final String entityKey,
+            final int page,
+            final int itemsPerPage,
+            final AuditElements.EventCategoryType type,
+            final String category,
+            final String subcategory,
+            final List<String> events,
+            final AuditElements.Result result,
+            final List<OrderByClause> orderBy) {
+
+        SearchRequest request = new SearchRequest.Builder().
+                index(ElasticsearchUtils.getAuditIndex(AuthContextUtils.getDomain())).
+                searchType(SearchType.QueryThenFetch).
+                query(getQuery(entityKey, type, category, subcategory, events, result)).
+                from(itemsPerPage * (page <= 0 ? 0 : page - 1)).
+                size(itemsPerPage < 0 ? indexMaxResultWindow : itemsPerPage).
+                sort(sortBuilders(orderBy)).
+                build();
+
+        @SuppressWarnings("rawtypes")
+        List<Hit<Map>> esResult = null;
+        try {
+            esResult = client.search(request, Map.class).hits().hits();
+        } catch (Exception e) {
+            LOG.error("While searching in Elasticsearch", e);
+        }
+
+        return CollectionUtils.isEmpty(esResult)
+                ? List.of()
+                : esResult.stream().
+                        map(hit -> POJOHelper.convertValue(hit.source().get("message"), AuditEntry.class)).
+                        filter(Objects::nonNull).collect(Collectors.toList());
+    }
+}
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 ab48f1cab9..2145da00ac 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
@@ -54,9 +54,9 @@ import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
 import org.apache.syncope.core.provisioning.api.utils.RealmUtils;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.apache.syncope.ext.elasticsearch.client.ElasticsearchUtils;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.MockedStatic;
 import org.mockito.Mockito;
@@ -81,12 +81,24 @@ public class ElasticsearchAnySearchDAOTest {
     @Mock
     private AnyUtilsFactory anyUtilsFactory;
 
-    @Mock
-    private ElasticsearchUtils elasticsearchUtils;
-
-    @InjectMocks
     private ElasticsearchAnySearchDAO searchDAO;
 
+    @BeforeEach
+    protected void setupSearchDAO() {
+        searchDAO = new ElasticsearchAnySearchDAO(
+                realmDAO,
+                dynRealmDAO,
+                null,
+                groupDAO,
+                null,
+                null,
+                entityFactory,
+                anyUtilsFactory,
+                null,
+                null,
+                10000);
+    }
+
     @Test
     public void getAdminRealmsFilter4realm() throws IOException {
         // 1. mock
@@ -152,7 +164,7 @@ public class ElasticsearchAnySearchDAOTest {
         when(groupDAO.findKey("groupKey")).thenReturn("groupKey");
 
         try (MockedStatic<ElasticsearchUtils> utils = Mockito.mockStatic(ElasticsearchUtils.class)) {
-            utils.when(() -> ElasticsearchUtils.getContextDomainName(
+            utils.when(() -> ElasticsearchUtils.getAnyIndex(
                     SyncopeConstants.MASTER_DOMAIN, AnyTypeKind.USER)).thenReturn("master_user");
 
             // 2. test
@@ -162,7 +174,7 @@ public class ElasticsearchAnySearchDAOTest {
             anyCond.setSchema("id");
 
             SearchRequest request = new SearchRequest.Builder().
-                    index(ElasticsearchUtils.getContextDomainName(AuthContextUtils.getDomain(), AnyTypeKind.USER)).
+                    index(ElasticsearchUtils.getAnyIndex(AuthContextUtils.getDomain(), AnyTypeKind.USER)).
                     searchType(SearchType.QueryThenFetch).
                     query(searchDAO.getQuery(realmDAO.findByFullPath("/any"), true,
                             adminRealms, SearchCond.getLeaf(anyCond), AnyTypeKind.USER)).
diff --git a/ext/elasticsearch/pom.xml b/ext/elasticsearch/pom.xml
index 649af13516..a9dcf7e532 100644
--- a/ext/elasticsearch/pom.xml
+++ b/ext/elasticsearch/pom.xml
@@ -41,6 +41,7 @@ under the License.
     <module>client-elasticsearch</module>
     <module>persistence-jpa</module>
     <module>provisioning-java</module>
+    <module>logic</module>
   </modules>
 
 </project>
diff --git a/ext/elasticsearch/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ElasticsearchReindex.java b/ext/elasticsearch/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ElasticsearchReindex.java
index 40b0133a96..8addb804ef 100644
--- a/ext/elasticsearch/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ElasticsearchReindex.java
+++ b/ext/elasticsearch/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/ElasticsearchReindex.java
@@ -74,16 +74,24 @@ public class ElasticsearchReindex extends AbstractSchedTaskJobDelegate<SchedTask
         return indexManager.defaultSettings();
     }
 
+    protected IndexSettings auditSettings() throws IOException {
+        return indexManager.defaultSettings();
+    }
+
     protected TypeMapping userMapping() throws IOException {
-        return indexManager.defaultMapping();
+        return indexManager.defaultAnyMapping();
     }
 
     protected TypeMapping groupMapping() throws IOException {
-        return indexManager.defaultMapping();
+        return indexManager.defaultAnyMapping();
     }
 
     protected TypeMapping anyObjectMapping() throws IOException {
-        return indexManager.defaultMapping();
+        return indexManager.defaultAnyMapping();
+    }
+
+    protected TypeMapping auditMapping() throws IOException {
+        return indexManager.defaultAuditMapping();
     }
 
     @Override
@@ -94,21 +102,20 @@ public class ElasticsearchReindex extends AbstractSchedTaskJobDelegate<SchedTask
             LOG.debug("Start rebuilding indexes");
 
             try {
-                indexManager.createIndex(
+                indexManager.createAnyIndex(
                         AuthContextUtils.getDomain(), AnyTypeKind.USER, userSettings(), userMapping());
 
-                indexManager.createIndex(
+                indexManager.createAnyIndex(
                         AuthContextUtils.getDomain(), AnyTypeKind.GROUP, groupSettings(), groupMapping());
 
-                indexManager.createIndex(
+                indexManager.createAnyIndex(
                         AuthContextUtils.getDomain(), AnyTypeKind.ANY_OBJECT, anyObjectSettings(), anyObjectMapping());
 
                 LOG.debug("Indexing users...");
                 for (int page = 1; page <= (userDAO.count() / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
                     for (String user : userDAO.findAllKeys(page, AnyDAO.DEFAULT_PAGE_SIZE)) {
                         IndexRequest<Map<String, Object>> request = new IndexRequest.Builder<Map<String, Object>>().
-                                index(ElasticsearchUtils.getContextDomainName(
-                                        AuthContextUtils.getDomain(), AnyTypeKind.USER)).
+                                index(ElasticsearchUtils.getAnyIndex(AuthContextUtils.getDomain(), AnyTypeKind.USER)).
                                 id(user).
                                 document(utils.document(userDAO.find(user), AuthContextUtils.getDomain())).
                                 build();
@@ -125,8 +132,7 @@ public class ElasticsearchReindex extends AbstractSchedTaskJobDelegate<SchedTask
                 for (int page = 1; page <= (groupDAO.count() / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
                     for (String group : groupDAO.findAllKeys(page, AnyDAO.DEFAULT_PAGE_SIZE)) {
                         IndexRequest<Map<String, Object>> request = new IndexRequest.Builder<Map<String, Object>>().
-                                index(ElasticsearchUtils.getContextDomainName(
-                                        AuthContextUtils.getDomain(), AnyTypeKind.GROUP)).
+                                index(ElasticsearchUtils.getAnyIndex(AuthContextUtils.getDomain(), AnyTypeKind.GROUP)).
                                 id(group).
                                 document(utils.document(groupDAO.find(group), AuthContextUtils.getDomain())).
                                 build();
@@ -143,7 +149,7 @@ public class ElasticsearchReindex extends AbstractSchedTaskJobDelegate<SchedTask
                 for (int page = 1; page <= (anyObjectDAO.count() / AnyDAO.DEFAULT_PAGE_SIZE) + 1; page++) {
                     for (String anyObject : anyObjectDAO.findAllKeys(page, AnyDAO.DEFAULT_PAGE_SIZE)) {
                         IndexRequest<Map<String, Object>> request = new IndexRequest.Builder<Map<String, Object>>().
-                                index(ElasticsearchUtils.getContextDomainName(
+                                index(ElasticsearchUtils.getAnyIndex(
                                         AuthContextUtils.getDomain(), AnyTypeKind.ANY_OBJECT)).
                                 id(anyObject).
                                 document(utils.document(anyObjectDAO.find(anyObject), AuthContextUtils.getDomain())).
@@ -157,6 +163,9 @@ public class ElasticsearchReindex extends AbstractSchedTaskJobDelegate<SchedTask
                     }
                 }
 
+                indexManager.createAuditIndex(
+                        AuthContextUtils.getDomain(), auditSettings(), auditMapping());
+
                 LOG.debug("Rebuild indexes for domain {} successfully completed", AuthContextUtils.getDomain());
             } catch (Exception e) {
                 throw new JobExecutionException("While rebuilding index for domain " + AuthContextUtils.getDomain(), e);
diff --git a/fit/core-reference/pom.xml b/fit/core-reference/pom.xml
index 126d829a4a..2db98ef49d 100644
--- a/fit/core-reference/pom.xml
+++ b/fit/core-reference/pom.xml
@@ -497,6 +497,11 @@ under the License.
       </properties>
 
       <dependencies>
+        <dependency>
+          <groupId>org.apache.syncope.ext.elasticsearch</groupId>
+          <artifactId>syncope-ext-elasticsearch-logic</artifactId>
+          <version>${project.version}</version>
+        </dependency>
         <dependency>
           <groupId>org.apache.syncope.ext.elasticsearch</groupId>
           <artifactId>syncope-ext-elasticsearch-provisioning-java</artifactId>
@@ -535,6 +540,7 @@ under the License.
                       <cluster.name>elasticsearch</cluster.name>
                       <xpack.security.enabled>false</xpack.security.enabled>
                       <ingest.geoip.downloader.enabled>false</ingest.geoip.downloader.enabled>
+                      <ES_JAVA_OPTS>-Xms750m -Xmx750m</ES_JAVA_OPTS>
                     </env>
                     <ports>
                       <port>9200:9200</port>
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CoreReferenceContext.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CoreReferenceContext.java
index 81534b7ac6..29eead687e 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CoreReferenceContext.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CoreReferenceContext.java
@@ -18,8 +18,10 @@
  */
 package org.apache.syncope.fit.core.reference;
 
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.core.logic.IdRepoLogicContext;
 import org.apache.syncope.core.logic.TaskLogic;
+import org.apache.syncope.core.logic.audit.AuditAppender;
 import org.apache.syncope.core.persistence.api.ImplementationLookup;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
@@ -59,4 +61,14 @@ public class CoreReferenceContext {
 
         return new ITImplementationLookup(uwf, anySearchDAO, enableFlowableForTestUsers, elasticsearchInit);
     }
+
+    @Bean
+    public AuditAppender testFileAuditAppender() {
+        return new TestFileAuditAppender(SyncopeConstants.MASTER_DOMAIN);
+    }
+
+    @Bean
+    public AuditAppender testFileRewriteAuditAppender() {
+        return new TestFileRewriteAuditAppender(SyncopeConstants.MASTER_DOMAIN);
+    }
 }
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
index a63671f3f9..feffedfb01 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
@@ -125,9 +125,6 @@ public class ITImplementationLookup implements ImplementationLookup {
                     DummyPushCorrelationRuleConf.class, DummyPushCorrelationRule.class,
                     DefaultPushCorrelationRuleConf.class, DefaultPushCorrelationRule.class);
 
-    private static final Set<Class<?>> AUDITAPPENDER_CLASSES =
-            Set.of(TestFileAuditAppender.class, TestFileRewriteAuditAppender.class);
-
     private static final Set<Class<?>> PROVISION_SORTER_CLASSES =
             Set.of(DefaultProvisionSorter.class);
 
@@ -216,10 +213,6 @@ public class ITImplementationLookup implements ImplementationLookup {
             classNames.add(TestNotificationRecipientsProvider.class.getName());
             put(IdRepoImplementationType.RECIPIENTS_PROVIDER, classNames);
 
-            classNames = ITImplementationLookup.AUDITAPPENDER_CLASSES.stream().
-                    map(Class::getName).collect(Collectors.toSet());
-            put(IdRepoImplementationType.AUDIT_APPENDER, classNames);
-
             classNames = ITImplementationLookup.PROVISION_SORTER_CLASSES.stream().
                     map(Class::getName).collect(Collectors.toSet());
             put(IdMImplementationType.PROVISION_SORTER, classNames);
@@ -327,9 +320,4 @@ public class ITImplementationLookup implements ImplementationLookup {
 
         return PUSH_CR_CLASSES.get(pushCorrelationRuleConfClass);
     }
-
-    @Override
-    public Set<Class<?>> getAuditAppenderClasses() {
-        return AUDITAPPENDER_CLASSES;
-    }
 }
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/SyslogRewriteAuditAppender.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/SyslogRewriteAuditAppender.java
index 6d15fc39b1..6918b73bc2 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/SyslogRewriteAuditAppender.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/SyslogRewriteAuditAppender.java
@@ -32,6 +32,19 @@ import org.apache.syncope.core.logic.audit.DefaultRewriteAuditAppender;
 
 public class SyslogRewriteAuditAppender extends DefaultRewriteAuditAppender {
 
+    public SyslogRewriteAuditAppender(final String domain) {
+        super(domain);
+
+        targetAppender = SyslogAppender.newSyslogAppenderBuilder().
+                setName(getTargetAppenderName()).
+                setHost("localhost").
+                setPort(514).
+                setProtocol(Protocol.UDP).
+                setLayout(PatternLayout.newBuilder().withPattern("%d{ISO8601} %-5level %logger - %msg%n").build()).
+                setFacility(Facility.LOCAL1).
+                build();
+    }
+
     @Override
     public Set<AuditLoggerName> getEvents() {
         Set<AuditLoggerName> events = new HashSet<>();
@@ -56,18 +69,6 @@ public class SyslogRewriteAuditAppender extends DefaultRewriteAuditAppender {
         return events;
     }
 
-    @Override
-    protected void initTargetAppender() {
-        targetAppender = SyslogAppender.newSyslogAppenderBuilder().
-                setName(getTargetAppenderName()).
-                setHost("localhost").
-                setPort(514).
-                setProtocol(Protocol.UDP).
-                setLayout(PatternLayout.newBuilder().withPattern("%d{ISO8601} %-5level %logger - %msg%n").build()).
-                setFacility(Facility.LOCAL1).
-                build();
-    }
-
     @Override
     public String getTargetAppenderName() {
         return "audit_for_" + domain + "_syslog";
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileAuditAppender.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileAuditAppender.java
index 8ae76232d7..ab542ffe18 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileAuditAppender.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileAuditAppender.java
@@ -35,26 +35,9 @@ import org.apache.syncope.core.logic.audit.DefaultAuditAppender;
 
 public class TestFileAuditAppender extends DefaultAuditAppender {
 
-    @Override
-    public Set<AuditLoggerName> getEvents() {
-        Set<AuditLoggerName> events = new HashSet<>();
-        events.add(new AuditLoggerName(
-                AuditElements.EventCategoryType.LOGIC,
-                ResourceLogic.class.getSimpleName(),
-                null,
-                "create",
-                AuditElements.Result.SUCCESS));
-        events.add(new AuditLoggerName(
-                AuditElements.EventCategoryType.LOGIC,
-                ConnectorLogic.class.getSimpleName(),
-                null,
-                "update",
-                AuditElements.Result.SUCCESS));
-        return events;
-    }
+    public TestFileAuditAppender(final String domain) {
+        super(domain);
 
-    @Override
-    protected void initTargetAppender() {
         LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
         // get log file path from existing file appender
         RollingRandomAccessFileAppender main =
@@ -75,6 +58,24 @@ public class TestFileAuditAppender extends DefaultAuditAppender {
                 build();
     }
 
+    @Override
+    public Set<AuditLoggerName> getEvents() {
+        Set<AuditLoggerName> events = new HashSet<>();
+        events.add(new AuditLoggerName(
+                AuditElements.EventCategoryType.LOGIC,
+                ResourceLogic.class.getSimpleName(),
+                null,
+                "create",
+                AuditElements.Result.SUCCESS));
+        events.add(new AuditLoggerName(
+                AuditElements.EventCategoryType.LOGIC,
+                ConnectorLogic.class.getSimpleName(),
+                null,
+                "update",
+                AuditElements.Result.SUCCESS));
+        return events;
+    }
+
     @Override
     public String getTargetAppenderName() {
         return "audit_for_" + domain + "_norewrite_file";
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileRewriteAuditAppender.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileRewriteAuditAppender.java
index 9762fb73fd..815bc52fae 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileRewriteAuditAppender.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestFileRewriteAuditAppender.java
@@ -34,18 +34,9 @@ import org.apache.syncope.core.logic.audit.DefaultRewriteAuditAppender;
 
 public class TestFileRewriteAuditAppender extends DefaultRewriteAuditAppender {
 
-    @Override
-    public Set<AuditLoggerName> getEvents() {
-        return Collections.singleton(new AuditLoggerName(
-                AuditElements.EventCategoryType.LOGIC,
-                ResourceLogic.class.getSimpleName(),
-                null,
-                "update",
-                AuditElements.Result.SUCCESS));
-    }
+    public TestFileRewriteAuditAppender(final String domain) {
+        super(domain);
 
-    @Override
-    protected void initTargetAppender() {
         LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
 
         // get log file path from existing file appender
@@ -63,6 +54,16 @@ public class TestFileRewriteAuditAppender extends DefaultRewriteAuditAppender {
                 build();
     }
 
+    @Override
+    public Set<AuditLoggerName> getEvents() {
+        return Collections.singleton(new AuditLoggerName(
+                AuditElements.EventCategoryType.LOGIC,
+                ResourceLogic.class.getSimpleName(),
+                null,
+                "update",
+                AuditElements.Result.SUCCESS));
+    }
+
     @Override
     public String getTargetAppenderName() {
         return "audit_for_" + domain + "_file";
diff --git a/fit/core-reference/src/main/resources/core-elasticsearch.properties b/fit/core-reference/src/main/resources/core-elasticsearch.properties
index 3360988643..d426e44d97 100644
--- a/fit/core-reference/src/main/resources/core-elasticsearch.properties
+++ b/fit/core-reference/src/main/resources/core-elasticsearch.properties
@@ -15,4 +15,9 @@
 # specific language governing permissions and limitations
 # under the License.
 
-persistence.anySearchDao=org.apache.syncope.core.persistence.jpa.dao.ElasticsearchAnySearchDAO
+elasticsearch.hostname=localhost
+elasticsearch.port=9200
+elasticsearch.scheme=http
+elasticsearch.indexMaxResultWindow=10000
+elasticsearch.numberOfShards=1
+elasticsearch.numberOfReplicas=1
diff --git a/fit/core-reference/src/main/resources/core-embedded.properties b/fit/core-reference/src/main/resources/core-embedded.properties
index 3926b090e6..d0d0fc45f2 100644
--- a/fit/core-reference/src/main/resources/core-embedded.properties
+++ b/fit/core-reference/src/main/resources/core-embedded.properties
@@ -14,6 +14,9 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
+
+management.endpoints.web.exposure.include=health,info,beans,env,loggers,entityCache
+
 keymaster.address=http://localhost:9080/syncope/rest/keymaster
 keymaster.username=${anonymousUser}
 keymaster.password=${anonymousKey}
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 c47aee07f0..511d0c1d5e 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
@@ -22,6 +22,7 @@ import static org.awaitility.Awaitility.await;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.json.JsonMapper;
 import com.fasterxml.jackson.dataformat.xml.XmlMapper;
 import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
@@ -49,6 +50,7 @@ import javax.naming.directory.ModificationItem;
 import javax.sql.DataSource;
 import javax.ws.rs.core.GenericType;
 import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
@@ -385,6 +387,10 @@ public abstract class AbstractITCase {
 
     protected static ImpersonationService IMPERSONATION_SERVICE;
 
+    protected static boolean IS_FLOWABLE_ENABLED = false;
+
+    protected static boolean IS_ELASTICSEARCH_ENABLED = false;
+
     @BeforeAll
     public static void securitySetup() {
         try (InputStream propStream = AbstractITCase.class.getResourceAsStream("/core.properties")) {
@@ -473,6 +479,19 @@ public abstract class AbstractITCase {
         WA_CONFIG_SERVICE = ADMIN_CLIENT.getService(WAConfigService.class);
     }
 
+    @BeforeAll
+    public static void actuatorInfoSetup() throws IOException {
+        JsonNode beans = JSON_MAPPER.readTree(
+                (InputStream) WebClient.create(StringUtils.substringBeforeLast(ADDRESS, "/") + "/actuator/beans").
+                        accept(MediaType.APPLICATION_JSON).get().getEntity());
+
+        JsonNode uwfAdapter = beans.findValues("uwfAdapter").get(0);
+        IS_FLOWABLE_ENABLED = uwfAdapter.get("resource").asText().contains("Flowable");
+
+        JsonNode anySearchDAO = beans.findValues("anySearchDAO").get(0);
+        IS_ELASTICSEARCH_ENABLED = anySearchDAO.get("type").asText().contains("Elasticsearch");
+    }
+
     protected static String getUUIDString() {
         return UUID.randomUUID().toString().substring(0, 8);
     }
@@ -943,6 +962,14 @@ public abstract class AbstractITCase {
     }
 
     protected static List<AuditEntry> query(final AuditQuery query, final int maxWaitSeconds) {
+        if (IS_ELASTICSEARCH_ENABLED) {
+            try {
+                Thread.sleep(2000);
+            } catch (InterruptedException ex) {
+                // ignore
+            }
+        }
+
         int i = 0;
         List<AuditEntry> results = List.of();
         do {
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractUIITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractUIITCase.java
index df9064d46e..ca5499f791 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractUIITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractUIITCase.java
@@ -22,12 +22,18 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.fail;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.Serializable;
 import java.lang.reflect.Method;
 import java.util.Properties;
 import java.util.Set;
 import java.util.stream.Collectors;
+import javax.ws.rs.core.MediaType;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.syncope.common.rest.api.service.SyncopeService;
 import org.apache.wicket.Component;
 import org.apache.wicket.MarkupContainer;
@@ -46,6 +52,8 @@ public abstract class AbstractUIITCase {
 
     protected static final Logger LOG = LoggerFactory.getLogger(AbstractUIITCase.class);
 
+    protected static final JsonMapper JSON_MAPPER = JsonMapper.builder().findAndAddModules().build();
+
     protected static final String ADMIN_UNAME = "admin";
 
     protected static final String ADMIN_PWD = "password";
@@ -64,6 +72,10 @@ public abstract class AbstractUIITCase {
 
     protected static SyncopeService SYNCOPE_SERVICE;
 
+    protected static boolean IS_FLOWABLE_ENABLED = false;
+
+    protected static boolean IS_ELASTICSEARCH_ENABLED = false;
+            
     @BeforeAll
     public static void securitySetup() {
         try (InputStream propStream = AbstractITCase.class.getResourceAsStream("/core.properties")) {
@@ -80,6 +92,19 @@ public abstract class AbstractUIITCase {
         assertNotNull(ANONYMOUS_KEY);
     }
 
+    @BeforeAll
+    public static void actuatorInfoSetup() throws IOException {
+        JsonNode beans = JSON_MAPPER.readTree(
+                (InputStream) WebClient.create(StringUtils.substringBeforeLast(ADDRESS, "/") + "/actuator/beans").
+                        accept(MediaType.APPLICATION_JSON).get().getEntity());
+
+        JsonNode uwfAdapter = beans.findValues("uwfAdapter").get(0);
+        IS_FLOWABLE_ENABLED = uwfAdapter.get("resource").asText().contains("Flowable");
+
+        JsonNode anySearchDAO = beans.findValues("anySearchDAO").get(0);
+        IS_ELASTICSEARCH_ENABLED = anySearchDAO.get("type").asText().contains("Elasticsearch");
+    }
+
     protected static <V extends Serializable> Component findComponentByProp(
             final String property, final String path, final V key) {
 
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/ElasticsearchDetector.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/ElasticsearchDetector.java
deleted file mode 100644
index c643df552c..0000000000
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/ElasticsearchDetector.java
+++ /dev/null
@@ -1,32 +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.fit;
-
-import org.apache.syncope.common.lib.info.PlatformInfo;
-
-public final class ElasticsearchDetector {
-
-    public static boolean isElasticSearchEnabled(final PlatformInfo platform) {
-        return platform.getPersistenceInfo().getAnySearchDAO().contains("Elasticsearch");
-    }
-    
-    private ElasticsearchDetector() {
-        // private constructor for static utility class
-    }    
-}
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/FlowableDetector.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/FlowableDetector.java
deleted file mode 100644
index a35bd69e40..0000000000
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/FlowableDetector.java
+++ /dev/null
@@ -1,32 +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.fit;
-
-import org.apache.syncope.common.lib.info.PlatformInfo;
-
-public final class FlowableDetector {
-
-    public static boolean isFlowableEnabledForUserWorkflow(final PlatformInfo platform) {
-        return platform.getWorkflowInfo().getUserWorkflowAdapter().contains("Flowable");
-    }
-
-    private FlowableDetector() {
-        // private constructor for static utility class
-    }
-}
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java
index 03dfcaee69..27b9e07771 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java
@@ -85,7 +85,14 @@ import org.junit.jupiter.api.Test;
 
 public class AuditITCase extends AbstractITCase {
 
-    private AuditEntry queryWithFailure(final AuditQuery query, final int maxWaitSeconds) {
+    private static AuditConfTO buildAuditConf(final String auditLoggerName, final boolean active) {
+        AuditConfTO auditConfTO = new AuditConfTO();
+        auditConfTO.setActive(active);
+        auditConfTO.setKey(auditLoggerName);
+        return auditConfTO;
+    }
+
+    private static AuditEntry queryWithFailure(final AuditQuery query, final int maxWaitSeconds) {
         List<AuditEntry> results = query(query, maxWaitSeconds);
         if (results.isEmpty()) {
             fail("Timeout when executing query for key " + query.getEntityKey());
@@ -392,6 +399,14 @@ public class AuditITCase extends AbstractITCase {
         auditEntry.setOutput(UUID.randomUUID().toString());
         assertDoesNotThrow(() -> AUDIT_SERVICE.create(auditEntry));
 
+        if (IS_ELASTICSEARCH_ENABLED) {
+            try {
+                Thread.sleep(2000);
+            } catch (InterruptedException ex) {
+                // ignore
+            }
+        }
+
         PagedResult<AuditEntry> events = AUDIT_SERVICE.search(new AuditQuery.Builder().
                 size(1).
                 type(auditEntry.getLogger().getType()).
@@ -419,6 +434,14 @@ public class AuditITCase extends AbstractITCase {
         auditEntry.setOutput(UUID.randomUUID().toString());
         assertDoesNotThrow(() -> AUDIT_SERVICE.create(auditEntry));
 
+        if (IS_ELASTICSEARCH_ENABLED) {
+            try {
+                Thread.sleep(2000);
+            } catch (InterruptedException ex) {
+                // ignore
+            }
+        }
+
         PagedResult<AuditEntry> events = AUDIT_SERVICE.search(new AuditQuery.Builder().
                 size(1).
                 type(auditEntry.getLogger().getType()).
@@ -461,7 +484,7 @@ public class AuditITCase extends AbstractITCase {
                     auditFilePath,
                     content -> content.contains(
                             "DEBUG Master.syncope.audit.[LOGIC]:[ResourceLogic]:[]:[update]:[SUCCESS]"
-                                    + " - This is a static test message"),
+                            + " - This is a static test message"),
                     10);
 
             // nothing expected in audit_for_Master_norewrite_file.log instead
@@ -469,7 +492,7 @@ public class AuditITCase extends AbstractITCase {
                     auditNoRewriteFilePath,
                     content -> !content.contains(
                             "DEBUG Master.syncope.audit.[LOGIC]:[ResourceLogic]:[]:[update]:[SUCCESS]"
-                                    + " - This is a static test message"),
+                            + " - This is a static test message"),
                     10);
         } catch (IOException e) {
             fail("Unable to read/write log files", e);
@@ -584,25 +607,32 @@ public class AuditITCase extends AbstractITCase {
             pullTaskTO.setDestinationRealm(SyncopeConstants.ROOT_REALM);
             pullTaskTO.setMatchingRule(MatchingRule.UPDATE);
             pullTaskTO.setUnmatchingRule(UnmatchingRule.ASSIGN);
-            RECONCILIATION_SERVICE.pull(
-                    new ReconQuery.Builder(AnyTypeKind.USER.name(), RESOURCE_NAME_LDAP).fiql("uid==pullFromLDAP")
-                            .build(),
-                    pullTaskTO);
+            RECONCILIATION_SERVICE.pull(new ReconQuery.Builder(AnyTypeKind.USER.name(), RESOURCE_NAME_LDAP).
+                    fiql("uid==pullFromLDAP").build(), pullTaskTO);
+
             // update pullTaskTO -> another audit entry
-            pullFromLDAP = updateUser(new UserUR.Builder(USER_SERVICE.read("pullFromLDAP").getKey())
-                    .plainAttr(new AttrPatch.Builder(new Attr.Builder("ctype").value("abcdef").build()).build())
-                    .build()).getEntity();
+            pullFromLDAP = updateUser(new UserUR.Builder(USER_SERVICE.read("pullFromLDAP").getKey()).
+                    plainAttr(new AttrPatch.Builder(new Attr.Builder("ctype").value("abcdef").build()).build()).
+                    build()).getEntity();
+
             // search by empty type and category events and get both events on testfromLDAP
-            assertEquals(2,
-                    AUDIT_SERVICE.search(new AuditQuery.Builder()
-                            .entityKey(pullFromLDAP.getKey())
-                            .page(1)
-                            .size(10)
-                            .events(List.of(
-                                    "create", "update", "matchingrule_update", "unmatchingrule_assign",
-                                    "unmatchingrule_provision"))
-                            .result(AuditElements.Result.SUCCESS)
-                            .build()).getTotalCount());
+            if (IS_ELASTICSEARCH_ENABLED) {
+                try {
+                    Thread.sleep(2000);
+                } catch (InterruptedException ex) {
+                    // ignore
+                }
+            }
+
+            assertEquals(2, AUDIT_SERVICE.search(new AuditQuery.Builder().
+                    entityKey(pullFromLDAP.getKey()).
+                    page(1).
+                    size(10).
+                    events(List.of(
+                            "create", "update", "matchingrule_update", "unmatchingrule_assign",
+                            "unmatchingrule_provision")).
+                    result(AuditElements.Result.SUCCESS).
+                    build()).getTotalCount());
         } finally {
             if (pullFromLDAP != null) {
                 USER_SERVICE.deassociate(new ResourceDR.Builder()
@@ -625,11 +655,4 @@ public class AuditITCase extends AbstractITCase {
             }
         }
     }
-
-    private static AuditConfTO buildAuditConf(final String auditLoggerName, final boolean active) {
-        AuditConfTO auditConfTO = new AuditConfTO();
-        auditConfTO.setActive(active);
-        auditConfTO.setKey(auditLoggerName);
-        return auditConfTO;
-    }
 }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java
index 58077f5b9d..bc343e50a0 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthenticationITCase.java
@@ -77,8 +77,6 @@ import org.apache.syncope.common.rest.api.service.SchemaService;
 import org.apache.syncope.common.rest.api.service.UserService;
 import org.apache.syncope.core.spring.security.Encryptor;
 import org.apache.syncope.fit.AbstractITCase;
-import org.apache.syncope.fit.ElasticsearchDetector;
-import org.apache.syncope.fit.FlowableDetector;
 import org.junit.jupiter.api.Test;
 import org.springframework.jdbc.core.JdbcTemplate;
 
@@ -201,7 +199,7 @@ public class AuthenticationITCase extends AbstractITCase {
         UserService userService2 = CLIENT_FACTORY.create(userTO.getUsername(), "password123").
                 getService(UserService.class);
 
-        if (ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform())) {
+        if (IS_ELASTICSEARCH_ENABLED) {
             try {
                 Thread.sleep(2000);
             } catch (InterruptedException ex) {
@@ -503,7 +501,7 @@ public class AuthenticationITCase extends AbstractITCase {
         assertEquals(2, member.getMemberships().size());
         String memberKey = member.getKey();
 
-        if (ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform())) {
+        if (IS_ELASTICSEARCH_ENABLED) {
             try {
                 Thread.sleep(2000);
             } catch (InterruptedException ex) {
@@ -588,7 +586,7 @@ public class AuthenticationITCase extends AbstractITCase {
 
     @Test
     public void issueSYNCOPE434() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform()));
+        assumeTrue(IS_FLOWABLE_ENABLED);
 
         // 1. create user with group 'groupForWorkflowApproval' 
         // (users with group groupForWorkflowApproval are defined in workflow as subject to approval)
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java
index 9bfbac72df..725d8adaf6 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/BpmnProcessITCase.java
@@ -32,7 +32,6 @@ import org.apache.commons.io.IOUtils;
 import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
 import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.fit.AbstractITCase;
-import org.apache.syncope.fit.FlowableDetector;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -44,7 +43,7 @@ public class BpmnProcessITCase extends AbstractITCase {
     @BeforeAll
     public static void findDefault() {
         assumeFalse(CLIENT_FACTORY.getContentType() == SyncopeClientFactoryBean.ContentType.YAML);
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform()));
+        assumeTrue(IS_FLOWABLE_ENABLED);
 
         BPMN_PROCESS_SERVICE.list().stream().
                 filter(BpmnProcess::isUserWorkflow).findAny().
@@ -55,7 +54,7 @@ public class BpmnProcessITCase extends AbstractITCase {
     @BeforeEach
     public void check() {
         assumeFalse(CLIENT_FACTORY.getContentType() == SyncopeClientFactoryBean.ContentType.YAML);
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform()));
+        assumeTrue(IS_FLOWABLE_ENABLED);
     }
 
     @Test
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/DelegationITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/DelegationITCase.java
index 9f11b30fd4..90cd2be1dd 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/DelegationITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/DelegationITCase.java
@@ -49,7 +49,6 @@ import org.apache.syncope.common.rest.api.service.DelegationService;
 import org.apache.syncope.common.rest.api.service.UserService;
 import org.apache.syncope.core.logic.UserLogic;
 import org.apache.syncope.fit.AbstractITCase;
-import org.apache.syncope.fit.ElasticsearchDetector;
 import org.junit.jupiter.api.Test;
 
 public class DelegationITCase extends AbstractITCase {
@@ -241,7 +240,7 @@ public class DelegationITCase extends AbstractITCase {
         // 3b. search users as rossini with delegation -> SUCCESS
         int forRossini = rossini.delegatedBy("bellini").getService(UserService.class).search(
                 new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).build()).getTotalCount();
-        if (!ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform())) {
+        if (!IS_ELASTICSEARCH_ENABLED) {
             assertEquals(forBellini, forRossini);
         }
 
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/DynRealmITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/DynRealmITCase.java
index 13234c1a70..7e26cd31dc 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/DynRealmITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/DynRealmITCase.java
@@ -54,7 +54,6 @@ import org.apache.syncope.common.rest.api.service.DynRealmService;
 import org.apache.syncope.common.rest.api.service.GroupService;
 import org.apache.syncope.common.rest.api.service.UserService;
 import org.apache.syncope.fit.AbstractITCase;
-import org.apache.syncope.fit.ElasticsearchDetector;
 import org.eclipse.jetty.client.HttpClient;
 import org.eclipse.jetty.client.api.ContentResponse;
 import org.eclipse.jetty.client.util.InputStreamContentProvider;
@@ -151,7 +150,7 @@ public class DynRealmITCase extends AbstractITCase {
             assertNotNull(group);
             final String groupKey = group.getKey();
 
-            if (ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform())) {
+            if (IS_ELASTICSEARCH_ENABLED) {
                 try {
                     Thread.sleep(2000);
                 } catch (InterruptedException ex) {
@@ -272,7 +271,7 @@ public class DynRealmITCase extends AbstractITCase {
             assertNotNull(user.getKey());
 
             // 4a. check that Elasticsearch index was updated correctly
-            if (ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform())) {
+            if (IS_ELASTICSEARCH_ENABLED) {
                 try {
                     Thread.sleep(2000);
                 } catch (InterruptedException ex) {
@@ -294,7 +293,7 @@ public class DynRealmITCase extends AbstractITCase {
             DYN_REALM_SERVICE.update(dynRealm);
 
             // 6a. check that Elasticsearch index was updated correctly
-            if (ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform())) {
+            if (IS_ELASTICSEARCH_ENABLED) {
                 try {
                     Thread.sleep(2000);
                 } catch (InterruptedException ex) {
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GroupITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GroupITCase.java
index 5d41a8ecf6..a859b95b1b 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GroupITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GroupITCase.java
@@ -99,7 +99,6 @@ import org.apache.syncope.common.rest.api.service.SyncopeService;
 import org.apache.syncope.core.provisioning.java.job.TaskJob;
 import org.apache.syncope.core.spring.security.Encryptor;
 import org.apache.syncope.fit.AbstractITCase;
-import org.apache.syncope.fit.ElasticsearchDetector;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
@@ -718,7 +717,7 @@ public class GroupITCase extends AbstractITCase {
         GroupTO group = createGroup(groupCR).getEntity();
         assertEquals(fiql, group.getADynMembershipConds().get(PRINTER));
 
-        if (ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform())) {
+        if (IS_ELASTICSEARCH_ENABLED) {
             try {
                 Thread.sleep(2000);
             } catch (InterruptedException ex) {
@@ -945,7 +944,7 @@ public class GroupITCase extends AbstractITCase {
 
     @Test
     public void provisionMembers() throws InterruptedException {
-        assumeFalse(ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform()));
+        assumeFalse(IS_ELASTICSEARCH_ENABLED);
 
         // 1. create group without resources
         GroupCR groupCR = getBasicSample("forProvision");
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/KeymasterITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/KeymasterITCase.java
index d093e3c2af..d77bfe5f1a 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/KeymasterITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/KeymasterITCase.java
@@ -54,7 +54,6 @@ import org.apache.syncope.common.rest.api.beans.AnyQuery;
 import org.apache.syncope.common.rest.api.service.UserService;
 import org.apache.syncope.core.spring.security.Encryptor;
 import org.apache.syncope.fit.AbstractITCase;
-import org.apache.syncope.fit.ElasticsearchDetector;
 import org.junit.jupiter.api.Test;
 
 public class KeymasterITCase extends AbstractITCase {
@@ -282,7 +281,7 @@ public class KeymasterITCase extends AbstractITCase {
         assertNotNull(user);
         assertEquals("monteverdi", user.getUsername());
 
-        if (ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform())) {
+        if (IS_ELASTICSEARCH_ENABLED) {
             try {
                 Thread.sleep(2000);
             } catch (InterruptedException ex) {
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java
index accc21204d..36ecb8a460 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MembershipITCase.java
@@ -56,7 +56,6 @@ import org.apache.syncope.common.lib.types.TaskType;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
 import org.apache.syncope.common.rest.api.service.TaskService;
 import org.apache.syncope.fit.AbstractITCase;
-import org.apache.syncope.fit.ElasticsearchDetector;
 import org.junit.jupiter.api.Test;
 import org.springframework.jdbc.core.JdbcTemplate;
 
@@ -279,7 +278,7 @@ public class MembershipITCase extends AbstractITCase {
             assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(execution.getStatus()));
 
             // 5. verify that pulled user has
-            if (ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform())) {
+            if (IS_ELASTICSEARCH_ENABLED) {
                 try {
                     Thread.sleep(2000);
                 } catch (InterruptedException ex) {
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MultitenancyITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MultitenancyITCase.java
index 1760969d11..a8c729c702 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MultitenancyITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MultitenancyITCase.java
@@ -70,7 +70,6 @@ import org.apache.syncope.common.rest.api.service.TaskService;
 import org.apache.syncope.common.rest.api.service.UserSelfService;
 import org.apache.syncope.common.rest.api.service.UserService;
 import org.apache.syncope.fit.AbstractITCase;
-import org.apache.syncope.fit.ElasticsearchDetector;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -208,7 +207,7 @@ public class MultitenancyITCase extends AbstractITCase {
             assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(status));
 
             // verify that pulled user is found
-            if (ElasticsearchDetector.isElasticSearchEnabled(ANONYMOUS_CLIENT.platform())) {
+            if (IS_ELASTICSEARCH_ENABLED) {
                 try {
                     Thread.sleep(2000);
                 } catch (InterruptedException ex) {
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
index b6c1571008..98aef995e4 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
@@ -109,8 +109,6 @@ import org.apache.syncope.common.rest.api.service.TaskService;
 import org.apache.syncope.core.provisioning.java.pushpull.DBPasswordPullActions;
 import org.apache.syncope.core.provisioning.java.pushpull.LDAPPasswordPullActions;
 import org.apache.syncope.core.spring.security.Encryptor;
-import org.apache.syncope.fit.ElasticsearchDetector;
-import org.apache.syncope.fit.FlowableDetector;
 import org.apache.syncope.fit.core.reference.TestPullActions;
 import org.identityconnectors.framework.common.objects.Name;
 import org.junit.jupiter.api.BeforeAll;
@@ -194,7 +192,7 @@ public class PullTaskITCase extends AbstractTaskITCase {
 
     @Test
     public void fromCSV() throws Exception {
-        assumeFalse(ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform()));
+        assumeFalse(IS_ELASTICSEARCH_ENABLED);
 
         removeTestUsers();
 
@@ -267,7 +265,7 @@ public class PullTaskITCase extends AbstractTaskITCase {
             UserTO userTO = USER_SERVICE.read(inUserTO.getKey());
             assertNotNull(userTO);
             assertEquals(userName, userTO.getUsername());
-            assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform())
+            assertEquals(IS_FLOWABLE_ENABLED
                     ? "active" : "created", userTO.getStatus());
             assertEquals("test9@syncope.apache.org", userTO.getPlainAttr("email").get().getValues().get(0));
             assertEquals("test9@syncope.apache.org", userTO.getPlainAttr("userId").get().getValues().get(0));
@@ -569,7 +567,7 @@ public class PullTaskITCase extends AbstractTaskITCase {
             // 4. pull
             execProvisioningTask(TASK_SERVICE, TaskType.PULL, pullTask.getKey(), MAX_WAIT_SECONDS, false);
 
-            if (ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform())) {
+            if (IS_ELASTICSEARCH_ENABLED) {
                 try {
                     Thread.sleep(2000);
                 } catch (InterruptedException ex) {
@@ -1082,7 +1080,7 @@ public class PullTaskITCase extends AbstractTaskITCase {
 
     @Test
     public void issueSYNCOPE307() {
-        assumeFalse(ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform()));
+        assumeFalse(IS_ELASTICSEARCH_ENABLED);
 
         UserCR userCR = UserITCase.getUniqueSample("s307@apache.org");
         userCR.setUsername("test0");
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 f8f4ab8b87..2be1f8767c 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
@@ -362,7 +362,6 @@ public class RealmITCase extends AbstractITCase {
             fail("This should not happen");
         } catch (SyncopeClientException e) {
             assertEquals(ClientExceptionType.RealmContains, e.getType());
-            assertEquals(5, e.getElements().size());
         }
     }
 
@@ -380,25 +379,24 @@ public class RealmITCase extends AbstractITCase {
         descendantRealm.getResources().add(RESOURCE_NAME_LDAP_ORGUNIT);
 
         // 2. check propagation
-        ProvisioningResult<RealmTO> result = REALM_SERVICE.create("/", realm).readEntity(
-            new GenericType<>() {
-            });
+        ProvisioningResult<RealmTO> result = REALM_SERVICE.create("/", realm).readEntity(new GenericType<>() {
+        });
         assertNotNull(result);
         assertEquals(1, result.getPropagationStatuses().size());
         assertEquals(RESOURCE_NAME_LDAP_ORGUNIT, result.getPropagationStatuses().get(0).getResource());
         assertEquals(ExecStatus.SUCCESS, result.getPropagationStatuses().get(0).getStatus());
 
         ProvisioningResult<RealmTO> resultChild = REALM_SERVICE.create("/test", childRealm).readEntity(
-            new GenericType<>() {
-            });
+                new GenericType<>() {
+        });
         assertNotNull(resultChild);
         assertEquals(1, resultChild.getPropagationStatuses().size());
         assertEquals(RESOURCE_NAME_LDAP_ORGUNIT, resultChild.getPropagationStatuses().get(0).getResource());
         assertEquals(ExecStatus.SUCCESS, resultChild.getPropagationStatuses().get(0).getStatus());
 
         ProvisioningResult<RealmTO> resultDescendant = REALM_SERVICE.create("/test/child", descendantRealm).readEntity(
-            new GenericType<>() {
-            });
+                new GenericType<>() {
+        });
         assertNotNull(resultDescendant);
         assertEquals(1, resultDescendant.getPropagationStatuses().size());
         assertEquals(RESOURCE_NAME_LDAP_ORGUNIT, resultDescendant.getPropagationStatuses().get(0).getResource());
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 4ac8ce9f8b..d28f681cfe 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
@@ -59,7 +59,6 @@ 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.RoleService;
 import org.apache.syncope.fit.AbstractITCase;
-import org.apache.syncope.fit.ElasticsearchDetector;
 import org.identityconnectors.framework.common.objects.Name;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -178,7 +177,7 @@ public class SearchITCase extends AbstractITCase {
         GroupTO group = createGroup(groupCR).getEntity();
         assertNotNull(group);
 
-        if (ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform())) {
+        if (IS_ELASTICSEARCH_ENABLED) {
             try {
                 Thread.sleep(2000);
             } catch (InterruptedException ex) {
@@ -236,7 +235,7 @@ public class SearchITCase extends AbstractITCase {
         role = getObject(response.getLocation(), RoleService.class, RoleTO.class);
         assertNotNull(role);
 
-        if (ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform())) {
+        if (IS_ELASTICSEARCH_ENABLED) {
             try {
                 Thread.sleep(2000);
             } catch (InterruptedException ex) {
@@ -333,7 +332,7 @@ public class SearchITCase extends AbstractITCase {
     public void searchByDate() {
         CLIENT_FACTORY.create("bellini", "password").self();
 
-        if (ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform())) {
+        if (IS_ELASTICSEARCH_ENABLED) {
             try {
                 Thread.sleep(2000);
             } catch (InterruptedException ex) {
@@ -687,7 +686,7 @@ public class SearchITCase extends AbstractITCase {
                     build();
             updateAnyObject(anyObjectUR);
 
-            if (ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform())) {
+            if (IS_ELASTICSEARCH_ENABLED) {
                 try {
                     Thread.sleep(2000);
                 } catch (InterruptedException ex) {
@@ -726,7 +725,7 @@ public class SearchITCase extends AbstractITCase {
         req.getPlainAttrs().add(new AttrPatch.Builder(attr("ctype", "ou=sample,o=isp")).build());
         USER_SERVICE.update(req);
 
-        if (ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform())) {
+        if (IS_ELASTICSEARCH_ENABLED) {
             try {
                 Thread.sleep(2000);
             } catch (InterruptedException ex) {
@@ -791,7 +790,7 @@ public class SearchITCase extends AbstractITCase {
             USER_SERVICE.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
                     fiql(SyncopeClient.getUserSearchConditionBuilder().is("userId").equalTo("*@apache.org").query()).
                     orderBy(SyncopeClient.getOrderByClauseBuilder().asc("surname").desc("firstname").build()).build());
-            if (!ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform())) {
+            if (!IS_ELASTICSEARCH_ENABLED) {
                 fail();
             }
         } catch (SyncopeClientException e) {
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
index fec1043d07..3d563da894 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
@@ -91,7 +91,6 @@ import org.apache.syncope.common.rest.api.service.UserSelfService;
 import org.apache.syncope.common.rest.api.service.UserService;
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 import org.apache.syncope.fit.AbstractITCase;
-import org.apache.syncope.fit.FlowableDetector;
 import org.apache.syncope.fit.core.reference.TestAccountRuleConf;
 import org.apache.syncope.fit.core.reference.TestPasswordRuleConf;
 import org.identityconnectors.framework.common.objects.OperationalAttributes;
@@ -703,7 +702,7 @@ public class UserITCase extends AbstractITCase {
 
     @Test
     public void createActivate() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform()));
+        assumeTrue(IS_FLOWABLE_ENABLED);
 
         UserCR userCR = getUniqueSample("createActivate@syncope.apache.org");
 
@@ -738,7 +737,7 @@ public class UserITCase extends AbstractITCase {
         UserTO userTO = createUser(userCR).getEntity();
 
         assertNotNull(userTO);
-        assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform())
+        assertEquals(IS_FLOWABLE_ENABLED
                 ? "active"
                 : "created", userTO.getStatus());
 
@@ -774,7 +773,7 @@ public class UserITCase extends AbstractITCase {
         userCR.getResources().add(RESOURCE_NAME_LDAP);
         UserTO userTO = createUser(userCR).getEntity();
         assertNotNull(userTO);
-        assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform())
+        assertEquals(IS_FLOWABLE_ENABLED
                 ? "active"
                 : "created", userTO.getStatus());
         String userKey = userTO.getKey();
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserIssuesITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserIssuesITCase.java
index c15b8e807f..aa3bf1ed5d 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserIssuesITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserIssuesITCase.java
@@ -85,7 +85,6 @@ import org.apache.syncope.core.provisioning.java.propagation.DBPasswordPropagati
 import org.apache.syncope.core.provisioning.java.propagation.LDAPPasswordPropagationActions;
 import org.apache.syncope.core.spring.security.Encryptor;
 import org.apache.syncope.fit.AbstractITCase;
-import org.apache.syncope.fit.ElasticsearchDetector;
 import org.identityconnectors.framework.common.objects.Name;
 import org.identityconnectors.framework.common.objects.OperationalAttributes;
 import org.junit.jupiter.api.Test;
@@ -1066,7 +1065,7 @@ public class UserIssuesITCase extends AbstractITCase {
 
     @Test
     public void issueSYNCOPE391() {
-        assumeFalse(ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform()));
+        assumeFalse(IS_ELASTICSEARCH_ENABLED);
 
         // 1. create user on Syncope with null password
         UserCR userCR = UserITCase.getUniqueSample("syncope391@syncope.apache.org");
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserRequestITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserRequestITCase.java
index eb63a93f26..5b3b7cfc84 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserRequestITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserRequestITCase.java
@@ -46,7 +46,6 @@ import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.rest.api.beans.UserRequestQuery;
 import org.apache.syncope.common.rest.api.service.UserRequestService;
 import org.apache.syncope.fit.AbstractITCase;
-import org.apache.syncope.fit.FlowableDetector;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -56,7 +55,7 @@ public class UserRequestITCase extends AbstractITCase {
     @BeforeAll
     public static void loadBpmnProcesses() throws IOException {
         assumeFalse(CLIENT_FACTORY.getContentType() == SyncopeClientFactoryBean.ContentType.YAML);
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform()));
+        assumeTrue(IS_FLOWABLE_ENABLED);
 
         WebClient.client(BPMN_PROCESS_SERVICE).type(MediaType.APPLICATION_XML_TYPE);
         BPMN_PROCESS_SERVICE.set("directorGroupRequest",
@@ -70,7 +69,7 @@ public class UserRequestITCase extends AbstractITCase {
     @BeforeEach
     public void check() {
         assumeFalse(CLIENT_FACTORY.getContentType() == SyncopeClientFactoryBean.ContentType.YAML);
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform()));
+        assumeTrue(IS_FLOWABLE_ENABLED);
     }
 
     @Test
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
index 5cf65843fb..c4596e7dad 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserSelfITCase.java
@@ -63,8 +63,6 @@ import org.apache.syncope.common.rest.api.service.UserRequestService;
 import org.apache.syncope.common.rest.api.service.UserSelfService;
 import org.apache.syncope.common.rest.api.service.UserService;
 import org.apache.syncope.fit.AbstractITCase;
-import org.apache.syncope.fit.ElasticsearchDetector;
-import org.apache.syncope.fit.FlowableDetector;
 import org.junit.jupiter.api.Test;
 import org.springframework.dao.EmptyResultDataAccessException;
 import org.springframework.jdbc.core.JdbcTemplate;
@@ -78,7 +76,7 @@ public class UserSelfITCase extends AbstractITCase {
 
     @Test
     public void create() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform()));
+        assumeTrue(IS_FLOWABLE_ENABLED);
 
         // 1. self-registration as admin: failure
         try {
@@ -100,7 +98,7 @@ public class UserSelfITCase extends AbstractITCase {
 
     @Test
     public void createAndApprove() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform()));
+        assumeTrue(IS_FLOWABLE_ENABLED);
 
         // 1. self-create user with membership: goes 'createApproval' with resources and membership but no propagation
         UserCR userCR = UserITCase.getUniqueSample("anonymous@syncope.apache.org");
@@ -139,7 +137,7 @@ public class UserSelfITCase extends AbstractITCase {
 
     @Test
     public void createAndUnclaim() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform()));
+        assumeTrue(IS_FLOWABLE_ENABLED);
 
         // 1. self-create user with membership: goes 'createApproval' with resources and membership but no propagation
         UserCR userCR = UserITCase.getUniqueSample("anonymous@syncope.apache.org");
@@ -232,14 +230,14 @@ public class UserSelfITCase extends AbstractITCase {
                 readEntity(new GenericType<ProvisioningResult<UserTO>>() {
                 }).getEntity();
         assertNotNull(updated);
-        assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform())
+        assertEquals(IS_FLOWABLE_ENABLED
                 ? "active" : "created", updated.getStatus());
         assertTrue(updated.getUsername().endsWith("XX"));
     }
 
     @Test
     public void updateWithApproval() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform()));
+        assumeTrue(IS_FLOWABLE_ENABLED);
 
         // 1. create user as admin
         UserTO created = createUser(UserITCase.getUniqueSample("anonymous@syncope.apache.org")).getEntity();
@@ -303,7 +301,7 @@ public class UserSelfITCase extends AbstractITCase {
                 new GenericType<ProvisioningResult<UserTO>>() {
         }).getEntity();
         assertNotNull(deleted);
-        assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform())
+        assertEquals(IS_FLOWABLE_ENABLED
                 ? "deleteApproval" : null, deleted.getStatus());
     }
 
@@ -340,7 +338,7 @@ public class UserSelfITCase extends AbstractITCase {
         }
         anonClient.getService(UserSelfService.class).requestPasswordReset(user.getUsername(), "Rossi");
 
-        if (ElasticsearchDetector.isElasticSearchEnabled(ADMIN_CLIENT.platform())) {
+        if (IS_ELASTICSEARCH_ENABLED) {
             try {
                 Thread.sleep(2000);
             } catch (InterruptedException ex) {
@@ -456,7 +454,7 @@ public class UserSelfITCase extends AbstractITCase {
 
     @Test
     public void createWithReject() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform()));
+        assumeTrue(IS_FLOWABLE_ENABLED);
 
         UserCR userCR = UserITCase.getUniqueSample("createWithReject@syncope.apache.org");
         userCR.getResources().add(RESOURCE_NAME_TESTDB);
@@ -532,7 +530,7 @@ public class UserSelfITCase extends AbstractITCase {
 
     @Test
     public void createWithApproval() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform()));
+        assumeTrue(IS_FLOWABLE_ENABLED);
 
         // read forms *before* any operation
         PagedResult<UserRequestForm> forms = USER_REQUEST_SERVICE.listForms(new UserRequestQuery.Builder().build());
@@ -619,7 +617,7 @@ public class UserSelfITCase extends AbstractITCase {
 
     @Test
     public void updateApproval() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform()));
+        assumeTrue(IS_FLOWABLE_ENABLED);
 
         // read forms *before* any operation
         PagedResult<UserRequestForm> forms = USER_REQUEST_SERVICE.listForms(
@@ -682,7 +680,7 @@ public class UserSelfITCase extends AbstractITCase {
 
     @Test
     public void availableTasks() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform()));
+        assumeTrue(IS_FLOWABLE_ENABLED);
 
         UserTO user = createUser(UserITCase.getUniqueSample("availableTasks@apache.org")).getEntity();
         assertEquals("active", user.getStatus());
@@ -696,7 +694,7 @@ public class UserSelfITCase extends AbstractITCase {
 
     @Test
     public void issueSYNCOPE15() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform()));
+        assumeTrue(IS_FLOWABLE_ENABLED);
 
         // read forms *before* any operation
         PagedResult<UserRequestForm> forms = USER_REQUEST_SERVICE.listForms(new UserRequestQuery.Builder().build());
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AuthenticatedITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AuthenticatedITCase.java
index 086463f7d8..5a64570ebc 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AuthenticatedITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AuthenticatedITCase.java
@@ -32,7 +32,6 @@ import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPasswordFieldPa
 import org.apache.syncope.common.lib.request.BooleanReplacePatchItem;
 import org.apache.syncope.common.lib.request.UserUR;
 import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.syncope.fit.FlowableDetector;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.util.tester.FormTester;
 import org.junit.jupiter.api.Test;
@@ -112,7 +111,7 @@ public class AuthenticatedITCase extends AbstractEnduserITCase {
 
         TESTER.assertRenderedPage(SelfResult.class);
 
-        assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(ADMIN_CLIENT.platform())
+        assertEquals(IS_FLOWABLE_ENABLED
                 ? "active" : "created", USER_SERVICE.read(username).getStatus());
         assertEquals(newEmail, USER_SERVICE.read(username).getPlainAttr("email").get().getValues().get(0));
 
diff --git a/pom.xml b/pom.xml
index 8d794ce417..dd1a717203 100644
--- a/pom.xml
+++ b/pom.xml
@@ -415,7 +415,7 @@ under the License.
     <bouncycastle.version>1.70</bouncycastle.version>
     <nimbus-jose-jwt.version>9.25.6</nimbus-jose-jwt.version>
 
-    <jackson.version>2.14.0-rc2</jackson.version>
+    <jackson.version>2.14.0-rc3</jackson.version>
 
     <spring-boot.version>2.7.5</spring-boot.version>
     <spring-cloud-gateway.version>3.1.4</spring-cloud-gateway.version>
@@ -432,7 +432,7 @@ under the License.
 
     <slf4j.version>1.7.36</slf4j.version>
 
-    <elasticsearch.version>8.4.3</elasticsearch.version>
+    <elasticsearch.version>8.5.0</elasticsearch.version>
 
     <apacheds.version>2.0.0.AM26</apacheds.version>
     <apachedirapi.version>2.0.0</apachedirapi.version>
@@ -786,6 +786,12 @@ under the License.
         <version>${jackson.version}</version>
       </dependency>
 
+      <dependency>
+        <groupId>com.fasterxml.woodstox</groupId>
+        <artifactId>woodstox-core</artifactId>
+        <version>6.4.0</version>
+      </dependency>
+
       <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-dependencies</artifactId>
diff --git a/src/main/asciidoc/reference-guide/concepts/audit.adoc b/src/main/asciidoc/reference-guide/concepts/audit.adoc
index 2e65916855..4a0cda5a42 100644
--- a/src/main/asciidoc/reference-guide/concepts/audit.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/audit.adoc
@@ -20,7 +20,8 @@
 
 The audit feature allows to capture <<audit-events,events>> occurring within the <<core>> and to log relevant information
 about them. +
-By default, events are logged as entries into the `AuditEntry` table of the internal storage.
+By default, events are logged as entries into the `AuditEntry` table of the internal storage. +
+Audit events can also be processed differently, for example when using the <<elasticsearch>> extension.
 
 Once events are reported, they can be used as input for external tools.
 
@@ -36,7 +37,7 @@ except for the admin console <<console-configuration-audit,tooling>>, which is n
 
 ==== Audit Appenders
 
-In addition to insertions into the `AuditEntry` table, events are also available for custom handling via Audit
+In addition to default processing, events are also available for custom handling via Audit
 Appenders, based on https://logging.apache.org/log4j/2.x/manual/appenders.html[Apache Log4j 2 Appenders^]. +
 This allows to empower the available implementations or to write new ones in order to route audit messages, with optional
 transformation (rewrite), to files, queues, sockets, syslog, etc.
diff --git a/src/main/asciidoc/reference-guide/concepts/extensions.adoc b/src/main/asciidoc/reference-guide/concepts/extensions.adoc
index e71ece95cb..1c09ec00fe 100644
--- a/src/main/asciidoc/reference-guide/concepts/extensions.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/extensions.adoc
@@ -101,11 +101,11 @@ This extension adds features to all components and layers that are available, an
 
 ==== Elasticsearch
 
-This extension provides an alternate internal search engine for <<users-groups-and-any-objects>>, requiring an external 
-https://www.elastic.co/[Elasticsearch^] cluster.
+This extension provides an alternate internal search engine for <<users-groups-and-any-objects>> and <<audit-events>>,
+requiring an external https://www.elastic.co/[Elasticsearch^] cluster.
 
 [WARNING]
-This extension supports Elasticsearch server versions starting from 7.x.
+This extension supports Elasticsearch server versions starting from 8.x.
 
 [TIP]
 As search operations are central for different aspects of the <<provisioning,provisioning process>>, the global
diff --git a/src/main/asciidoc/reference-guide/usage/customization.adoc b/src/main/asciidoc/reference-guide/usage/customization.adoc
index 95f46b8be7..2b065e1456 100644
--- a/src/main/asciidoc/reference-guide/usage/customization.adoc
+++ b/src/main/asciidoc/reference-guide/usage/customization.adoc
@@ -358,6 +358,11 @@ Add the following dependencies to `core/pom.xml`:
 
 [source,xml,subs="verbatim,attributes"]
 ----
+<dependency>
+  <groupId>org.apache.syncope.ext.elasticsearch</groupId>
+  <artifactId>syncope-ext-elasticsearch-logic</artifactId>
+  <version>${syncope.version}</version>
+</dependency>
 <dependency>
   <groupId>org.apache.syncope.ext.elasticsearch</groupId>
   <artifactId>syncope-ext-elasticsearch-provisioning-java</artifactId>
@@ -370,6 +375,24 @@ Add the following dependencies to `core/pom.xml`:
 </dependency>
 ----
 
+Create
+
+[source]
+....
+elasticsearch.hostname=localhost
+elasticsearch.port=9200
+elasticsearch.scheme=http
+elasticsearch.indexMaxResultWindow=10000
+elasticsearch.numberOfShards=1
+elasticsearch.numberOfReplicas=1
+....
+
+as `core/src/main/resources/core-elasticsearch.properties`.
+
+Do not forget to include `elasticsearch` as 
+https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.profiles.adding-active-profiles[Spring Boot profile^]
+for the Core application.
+
 If needed, customize the `@Bean` declarations from
 ifeval::["{snapshotOrRelease}" == "release"]
 https://github.com/apache/syncope/blob/syncope-{docVersion}/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchClientContext.java[ElasticsearchClientContext^]
@@ -381,12 +404,12 @@ as explained <<extending-configuration,above>>.
 
 It is also required to initialize the Elasticsearch indexes: add a new Java <<implementations,implementation>> for
 `TASKJOB_DELEGATE` and use `org.apache.syncope.core.provisioning.java.job.ElasticsearchReindex` as class. +
-Then, create a new <<tasks-scheduled, schedyled task>>, select the implementation just created as job delegate and execute it.
+Then, create a new <<tasks-scheduled, scheduled task>>, select the implementation just created as job delegate and execute it.
 
 [TIP]
 The `org.apache.syncope.core.provisioning.java.job.ElasticsearchReindex` task created above is not meant for
 scheduled execution; rather, it can be run every time you want to blank and re-create the Elasticsearch indexes
-starting from Syncope's users, groups and any objects.
+starting from Syncope's internal storage.
 
 [discrete]
 ===== Enable the <<SCIM>> extension