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 2021/07/13 07:23:35 UTC

[syncope] branch master updated: [SYNCOPE-1614] Spring Boot HealthIndicator and InfoContributor for al… (#277)

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 ce1c3c4  [SYNCOPE-1614] Spring Boot HealthIndicator and InfoContributor for al… (#277)
ce1c3c4 is described below

commit ce1c3c4458ead26d7dbfbe66470cf15708cf7d0f
Author: Francesco Chicchiriccò <il...@users.noreply.github.com>
AuthorDate: Tue Jul 13 09:23:25 2021 +0200

    [SYNCOPE-1614] Spring Boot HealthIndicator and InfoContributor for al… (#277)
---
 client/idrepo/common-ui/pom.xml                    |   6 +-
 .../syncope/client/ui/commons/BaseSession.java     |   3 +
 .../actuate/SyncopeCoreHealthIndicator.java        |  68 ++++
 client/idrepo/console/pom.xml                      |  39 ++-
 .../syncope/client/console/SecurityConfig.java     |  57 +++
 .../client/console/SyncopeConsoleApplication.java  |  28 +-
 .../client/console/SyncopeConsoleSession.java      |  32 +-
 .../client/console/SyncopeWebApplication.java      | 113 ++----
 .../syncope/client/console/pages/BasePage.java     |  14 +-
 .../client/console/panels/AnyTypesPanel.java       |   1 -
 .../console/panels/DashboardOverviewPanel.java     |  10 +-
 .../console/panels/DelegationDirectoryPanel.java   |   2 +-
 .../client/console/panels/UserDirectoryPanel.java  |   6 +-
 .../client/console/panels/search/SearchUtils.java  |   3 +-
 .../client/console/rest/AnyTypeRestClient.java     |   3 +-
 .../client/console/rest/RoleRestClient.java        |   4 +-
 .../client/console/rest/SyncopeRestClient.java     |  10 -
 .../src/main/resources/application.properties      |   3 +
 .../syncope/client/console/AbstractTest.java       |  42 ++-
 client/idrepo/enduser/pom.xml                      |  37 +-
 .../syncope/client/enduser/SecurityConfig.java     |  57 +++
 .../client/enduser/SyncopeEnduserApplication.java  |  28 +-
 .../client/enduser/SyncopeEnduserSession.java      |  24 +-
 .../client/enduser/SyncopeWebApplication.java      |  82 ++---
 .../syncope/client/enduser/pages/EditUser.java     |   3 +-
 .../client/enduser/pages/SelfRegistration.java     |   3 +-
 .../client/enduser/panels/AnyFormPanel.java        |   4 +-
 .../client/enduser/panels/UserFormPanel.java       |   2 -
 .../client/enduser/panels/UserSelfFormPanel.java   |   4 +-
 .../client/enduser/panels/any/AbstractAttrs.java   |  16 +-
 .../client/enduser/panels/any/DerAttrs.java        |   8 +-
 .../syncope/client/enduser/panels/any/Details.java |   8 +-
 .../syncope/client/enduser/panels/any/Groups.java  |   8 +-
 .../client/enduser/panels/any/PlainAttrs.java      |   6 +-
 .../client/enduser/panels/any/Resources.java       |   5 +-
 .../client/enduser/panels/any/SelfUserDetails.java |   6 +-
 .../client/enduser/panels/any/UserDetails.java     |  25 +-
 .../client/enduser/panels/any/VirAttrs.java        |  30 +-
 .../client/enduser/rest/AnyTypeRestClient.java     |   6 +-
 .../client/enduser/rest/SchemaRestClient.java      |   3 +-
 .../client/enduser/rest/SyncopeRestClient.java     |  16 +-
 .../client/enduser/rest/UserSelfRestClient.java    |   2 +-
 .../src/main/resources/application.properties      |   3 +
 .../syncope/client/enduser/AbstractTest.java       |  40 ++-
 .../apache/syncope/client/lib/SyncopeClient.java   |  72 +++-
 .../syncope/common/lib/info/PlatformInfo.java      |  20 --
 .../common/rest/api/service/SyncopeService.java    |  35 --
 core/am/logic/pom.xml                              |  18 -
 core/idm/logic/pom.xml                             |  18 -
 core/idrepo/logic/pom.xml                          |  18 -
 .../apache/syncope/core/logic/SyncopeLogic.java    | 383 ---------------------
 .../core/rest/cxf/service/SyncopeServiceImpl.java  |  18 -
 .../jpa/dao/JPAExternalResourceDAO.java            |   6 +-
 core/starter/pom.xml                               |  25 +-
 .../core/starter/SyncopeCoreApplication.java       |  36 ++
 .../starter/actuate/DomainsHealthIndicator.java    |  71 ++++
 .../actuate/ExternalResourcesHealthIndicator.java  |  91 +++++
 .../actuate/SyncopeCoreInfoContributor.java}       | 368 +++++++-------------
 .../src/main/resources/application.properties      |   3 +
 .../src/main/resources/application.properties      |   3 +
 .../core/src/main/resources/application.properties |   3 +
 .../src/main/resources/application.properties      |   3 +
 .../sra/src/main/resources/application.properties  |   5 +-
 .../wa/src/main/resources/application.properties   |   4 +-
 .../client/enduser/rest/UserRequestRestClient.java |   3 +-
 fit/core-reference/pom.xml                         |  18 -
 .../org/apache/syncope/fit/AbstractITCase.java     |  40 +--
 .../apache/syncope/fit/ElasticsearchDetector.java  |   6 +-
 .../org/apache/syncope/fit/FlowableDetector.java   |   6 +-
 .../syncope/fit/console/AbstractConsoleITCase.java |   2 +
 .../apache/syncope/fit/console/AnyTypesITCase.java |   2 +-
 .../org/apache/syncope/fit/core/AuditITCase.java   |   2 +-
 .../syncope/fit/core/AuthenticationITCase.java     |  10 +-
 .../apache/syncope/fit/core/BpmnProcessITCase.java |   4 +-
 .../apache/syncope/fit/core/CamelRouteITCase.java  |   2 +-
 .../apache/syncope/fit/core/ClientAppITCase.java   |   2 +-
 .../apache/syncope/fit/core/DynRealmITCase.java    |   6 +-
 .../apache/syncope/fit/core/KeymasterITCase.java   |   4 +-
 .../apache/syncope/fit/core/MembershipITCase.java  |   2 +-
 .../syncope/fit/core/MultitenancyITCase.java       |   2 +-
 .../org/apache/syncope/fit/core/PolicyITCase.java  |   4 +-
 .../apache/syncope/fit/core/PullTaskITCase.java    |   8 +-
 .../apache/syncope/fit/core/PushTaskITCase.java    |   2 +-
 .../org/apache/syncope/fit/core/ReportITCase.java  |   2 +-
 .../apache/syncope/fit/core/ResourceITCase.java    |   2 +-
 .../apache/syncope/fit/core/SchedTaskITCase.java   |   2 +-
 .../org/apache/syncope/fit/core/SearchITCase.java  |  12 +-
 .../org/apache/syncope/fit/core/UserITCase.java    |   6 +-
 .../apache/syncope/fit/core/UserIssuesITCase.java  |   2 +-
 .../apache/syncope/fit/core/UserRequestITCase.java |   4 +-
 .../apache/syncope/fit/core/UserSelfITCase.java    |  26 +-
 .../syncope/fit/enduser/AbstractEnduserITCase.java |   2 +
 .../syncope/fit/enduser/AuthenticatedITCase.java   |   2 +-
 .../netbeans/service/SyncopeManagerService.java    |   8 +-
 pom.xml                                            |  45 ++-
 sra/pom.xml                                        |  13 +
 .../apache/syncope/sra/SyncopeSRAApplication.java  |   9 +-
 .../sra/{actuator => actuate}/SRASessions.java     |   2 +-
 .../sra/actuate/SyncopeCoreHealthIndicator.java    |  68 ++++
 sra/src/main/resources/application.properties      |   5 +-
 wa/starter/pom.xml                                 |  19 +
 .../actuate/SyncopeCoreHealthIndicator.java        |  53 +++
 .../wa/starter/config/SyncopeWAConfiguration.java  |   8 +
 .../src/main/resources/application.properties      |   4 +-
 104 files changed, 1258 insertions(+), 1231 deletions(-)

diff --git a/client/idrepo/common-ui/pom.xml b/client/idrepo/common-ui/pom.xml
index 1e0c766..c231700 100644
--- a/client/idrepo/common-ui/pom.xml
+++ b/client/idrepo/common-ui/pom.xml
@@ -37,10 +37,10 @@ under the License.
   
   <dependencies>
     <dependency>
-      <groupId>org.springframework</groupId>
-      <artifactId>spring-context</artifactId>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-actuator</artifactId>
     </dependency>
-    
+
     <dependency>
       <groupId>org.apache.wicket</groupId>
       <artifactId>wicket</artifactId>
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/BaseSession.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/BaseSession.java
index f98a4d5..589b8b8 100644
--- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/BaseSession.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/BaseSession.java
@@ -21,6 +21,7 @@ package org.apache.syncope.client.ui.commons;
 import java.util.concurrent.Callable;
 import java.util.concurrent.Future;
 import org.apache.commons.lang3.time.FastDateFormat;
+import org.apache.syncope.client.lib.SyncopeClient;
 
 public interface BaseSession {
 
@@ -53,6 +54,8 @@ public interface BaseSession {
 
     String getJWT();
 
+    SyncopeClient getAnonymousClient();
+
     <T> T getAnonymousService(Class<T> serviceClass);
 
     <T> T getService(Class<T> serviceClass);
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/actuate/SyncopeCoreHealthIndicator.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/actuate/SyncopeCoreHealthIndicator.java
new file mode 100644
index 0000000..e7b6190
--- /dev/null
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/actuate/SyncopeCoreHealthIndicator.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.client.ui.commons.actuate;
+
+import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
+import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
+import org.apache.syncope.common.keymaster.client.api.ServiceOps;
+import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
+import org.apache.syncope.common.rest.api.service.UserSelfService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.boot.actuate.health.Status;
+
+public class SyncopeCoreHealthIndicator implements HealthIndicator {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(SyncopeCoreHealthIndicator.class);
+
+    @Autowired
+    protected ServiceOps serviceOps;
+
+    @Value("${anonymousUser}")
+    protected String anonymousUser;
+
+    @Value("${anonymousKey}")
+    protected String anonymousKey;
+
+    @Value("${useGZIPCompression:false}")
+    protected boolean useGZIPCompression;
+
+    @Override
+    public Health health() {
+        Health.Builder builder = new Health.Builder();
+
+        try {
+            new SyncopeClientFactoryBean().
+                    setAddress(serviceOps.get(NetworkService.Type.CORE).getAddress()).
+                    setUseCompression(useGZIPCompression).
+                    create(new AnonymousAuthenticationHandler(anonymousUser, anonymousKey)).
+                    getService(UserSelfService.class).read();
+            builder.status(Status.UP);
+        } catch (Exception e) {
+            LOG.debug("When attempting to connect to Syncope Core", e);
+            builder.status(Status.DOWN);
+        }
+
+        return builder.build();
+    }
+}
diff --git a/client/idrepo/console/pom.xml b/client/idrepo/console/pom.xml
index dc4ca1a..06e9391 100644
--- a/client/idrepo/console/pom.xml
+++ b/client/idrepo/console/pom.xml
@@ -52,7 +52,15 @@ under the License.
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-log4j2</artifactId>
     </dependency>
-      
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-actuator</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-security</artifactId>
+    </dependency>
+
     <dependency>
       <groupId>org.apache.wicket</groupId>
       <artifactId>wicket-auth-roles</artifactId>
@@ -138,11 +146,6 @@ under the License.
   <build>
     <plugins>
       <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-checkstyle-plugin</artifactId>
-      </plugin>
-      
-      <plugin>
         <groupId>nl.geodienstencentrum.maven</groupId>
         <artifactId>sass-maven-plugin</artifactId>
         <inherited>true</inherited>
@@ -167,6 +170,30 @@ under the License.
           <skip>${sass.skip}</skip>
         </configuration>
       </plugin>
+
+      <plugin>
+        <groupId>io.github.git-commit-id</groupId>
+        <artifactId>git-commit-id-maven-plugin</artifactId>
+        <configuration>
+          <failOnNoGitDirectory>false</failOnNoGitDirectory>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>build-info</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+      </plugin>
     </plugins>
     
     <resources>
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SecurityConfig.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SecurityConfig.java
new file mode 100644
index 0000000..075570b
--- /dev/null
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SecurityConfig.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.client.console;
+
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+@EnableWebSecurity
+@Configuration
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+    @Value("${anonymousUser}")
+    private String anonymousUser;
+
+    @Value("${anonymousKey}")
+    private String anonymousKey;
+
+    @Override
+    protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
+        auth.inMemoryAuthentication().
+                withUser(anonymousUser).
+                password("{noop}" + anonymousKey).
+                roles(IdRepoEntitlement.ANONYMOUS);
+    }
+
+    @Override
+    protected void configure(final HttpSecurity http) throws Exception {
+        http.csrf().disable().
+                authorizeRequests().
+                requestMatchers(EndpointRequest.toAnyEndpoint()).
+                authenticated().
+                and().
+                httpBasic();
+    }
+}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
index f0fb80f..9b4823e 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
@@ -43,6 +43,7 @@ import org.apache.syncope.client.console.init.ClassPathScanImplementationLookup;
 import org.apache.syncope.client.console.wizards.any.UserFormFinalizerUtils;
 import org.apache.syncope.client.ui.commons.ApplicationContextProvider;
 import org.apache.syncope.client.ui.commons.MIMETypesLoader;
+import org.apache.syncope.client.ui.commons.actuate.SyncopeCoreHealthIndicator;
 import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
@@ -56,7 +57,10 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
 import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.PropertySource;
 
+@PropertySource("classpath:console.properties")
+@PropertySource(value = "file:${console.directory}/console.properties", ignoreResourceNotFound = true)
 @SpringBootApplication(exclude = {
     ErrorMvcAutoConfiguration.class,
     HttpMessageConvertersAutoConfiguration.class })
@@ -76,18 +80,14 @@ public class SyncopeConsoleApplication extends SpringBootServletInitializer {
     }
 
     @Bean
-    public KeymasterStart keymasterStart() {
-        return new KeymasterStart(NetworkService.Type.CONSOLE);
-    }
-
-    @Bean
-    public KeymasterStop keymasterStop() {
-        return new KeymasterStop(NetworkService.Type.CONSOLE);
+    public ApplicationContextProvider applicationContextProvider() {
+        return new ApplicationContextProvider();
     }
 
+    @ConditionalOnMissingBean
     @Bean
-    public ApplicationContextProvider applicationContextProvider() {
-        return new ApplicationContextProvider();
+    public SyncopeCoreHealthIndicator syncopeCoreHealthIndicator() {
+        return new SyncopeCoreHealthIndicator();
     }
 
     @ConditionalOnMissingBean(name = "classPathScanImplementationLookup")
@@ -171,4 +171,14 @@ public class SyncopeConsoleApplication extends SpringBootServletInitializer {
     public PolicyTabProvider idRepoPolicyTabProvider() {
         return new IdRepoPolicyTabProvider();
     }
+
+    @Bean
+    public KeymasterStart keymasterStart() {
+        return new KeymasterStart(NetworkService.Type.CONSOLE);
+    }
+
+    @Bean
+    public KeymasterStop keymasterStop() {
+        return new KeymasterStop(NetworkService.Type.CONSOLE);
+    }
 }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
index 83de4f7..2ea6ef7 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
@@ -39,6 +39,7 @@ import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.commons.lang3.time.FastDateFormat;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.commons.lang3.tuple.Triple;
 import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.syncope.client.console.commons.RealmsUtils;
@@ -54,7 +55,6 @@ import org.apache.syncope.common.lib.info.PlatformInfo;
 import org.apache.syncope.common.lib.info.SystemInfo;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
-import org.apache.syncope.common.rest.api.service.SyncopeService;
 import org.apache.wicket.Session;
 import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
 import org.apache.wicket.authroles.authorization.strategies.role.Roles;
@@ -97,6 +97,8 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession implements Ba
 
     protected final SyncopeClient anonymousClient;
 
+    protected final Pair<String, String> gitAndBuildInfo;
+
     protected final PlatformInfo platformInfo;
 
     protected final SystemInfo systemInfo;
@@ -131,8 +133,9 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession implements Ba
                 SyncopeWebApplication.get().getAnonymousUser(),
                 SyncopeWebApplication.get().getAnonymousKey()));
 
-        platformInfo = anonymousClient.getService(SyncopeService.class).platform();
-        systemInfo = anonymousClient.getService(SyncopeService.class).system();
+        gitAndBuildInfo = anonymousClient.gitAndBuildInfo();
+        platformInfo = anonymousClient.platform();
+        systemInfo = anonymousClient.system();
 
         executor = new ThreadPoolTaskExecutor();
         executor.setWaitForTasksToCompleteOnShutdown(false);
@@ -176,10 +179,6 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession implements Ba
         return clientFactory.getContentType().getMediaType();
     }
 
-    public SyncopeClient getAnonymousClient() {
-        return anonymousClient;
-    }
-
     public void execute(final Runnable command) {
         try {
             executor.execute(command);
@@ -199,6 +198,10 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession implements Ba
         }
     }
 
+    public Pair<String, String> gitAndBuildInfo() {
+        return gitAndBuildInfo;
+    }
+
     public PlatformInfo getPlatformInfo() {
         return platformInfo;
     }
@@ -381,6 +384,16 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession implements Ba
         }
     }
 
+    @Override
+    public SyncopeClient getAnonymousClient() {
+        return anonymousClient;
+    }
+
+    @Override
+    public <T> T getAnonymousService(final Class<T> serviceClass) {
+        return getAnonymousClient().getService(serviceClass);
+    }
+
     @SuppressWarnings("unchecked")
     protected <T> T getCachedService(final Class<T> serviceClass) {
         T service;
@@ -397,11 +410,6 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession implements Ba
     }
 
     @Override
-    public <T> T getAnonymousService(final Class<T> serviceClass) {
-        return getAnonymousClient().getService(serviceClass);
-    }
-
-    @Override
     public <T> T getService(final Class<T> serviceClass) {
         return getCachedService(serviceClass);
     }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java
index a9e48da..6d1ec5b 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java
@@ -56,7 +56,6 @@ import org.apache.wicket.request.http.WebResponse;
 import org.apache.wicket.request.resource.AbstractResource;
 import org.apache.wicket.request.resource.IResource;
 import org.apache.wicket.request.resource.ResourceReference;
-import org.apache.wicket.util.lang.Args;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.apache.syncope.client.console.commons.ExternalResourceProvider;
@@ -76,13 +75,14 @@ import org.apache.wicket.request.component.IRequestablePage;
 import org.apache.wicket.request.cycle.IRequestCycleListener;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.ApplicationContext;
 import org.springframework.stereotype.Component;
 
 @Component
 public class SyncopeWebApplication extends WicketBootSecuredWebApplication {
 
-    private static final Logger LOG = LoggerFactory.getLogger(SyncopeWebApplication.class);
+    protected static final Logger LOG = LoggerFactory.getLogger(SyncopeWebApplication.class);
 
     private static final String CONSOLE_PROPERTIES = "console.properties";
 
@@ -91,56 +91,65 @@ public class SyncopeWebApplication extends WicketBootSecuredWebApplication {
     }
 
     @Autowired
-    private ClassPathScanImplementationLookup lookup;
+    protected ClassPathScanImplementationLookup lookup;
 
     @Autowired
-    private ServiceOps serviceOps;
+    protected ServiceOps serviceOps;
 
-    private String anonymousUser;
+    @Value("${anonymousUser}")
+    protected String anonymousUser;
 
-    private String anonymousKey;
+    @Value("${anonymousKey}")
+    protected String anonymousKey;
 
-    private String reconciliationReportKey;
+    @Value("${useGZIPCompression:false}")
+    protected boolean useGZIPCompression;
 
-    private boolean useGZIPCompression;
+    @Value("${maxUploadFileSizeMB:#{null}}")
+    protected Integer maxUploadFileSizeMB;
 
-    private Integer maxUploadFileSizeMB;
+    @Value("${maxWaitTime:30}")
+    protected Integer maxWaitTime;
 
-    private Integer maxWaitTime;
+    @Value("${corePoolSize:5}")
+    protected Integer corePoolSize;
 
-    private Integer corePoolSize;
+    @Value("${maxPoolSize:10}")
+    protected Integer maxPoolSize;
 
-    private Integer maxPoolSize;
+    @Value("${queueCapacity:50}")
+    protected Integer queueCapacity;
 
-    private Integer queueCapacity;
+    @Value("${reconciliationReportKey}")
+    protected String reconciliationReportKey;
 
     @Autowired
-    private ExternalResourceProvider resourceProvider;
+    protected ExternalResourceProvider resourceProvider;
 
     @Autowired
-    private AnyDirectoryPanelAdditionalActionsProvider anyDirectoryPanelAdditionalActionsProvider;
+    protected AnyDirectoryPanelAdditionalActionsProvider anyDirectoryPanelAdditionalActionsProvider;
 
     @Autowired
-    private AnyDirectoryPanelAdditionalActionLinksProvider anyDirectoryPanelAdditionalActionLinksProvider;
+    protected AnyDirectoryPanelAdditionalActionLinksProvider anyDirectoryPanelAdditionalActionLinksProvider;
 
     @Autowired
-    private AnyWizardBuilderAdditionalSteps anyWizardBuilderAdditionalSteps;
+    protected AnyWizardBuilderAdditionalSteps anyWizardBuilderAdditionalSteps;
 
     @Autowired
-    private StatusProvider statusProvider;
+    protected StatusProvider statusProvider;
 
     @Autowired
-    private VirSchemaDetailsPanelProvider virSchemaDetailsPanelProvider;
+    protected VirSchemaDetailsPanelProvider virSchemaDetailsPanelProvider;
 
     @Autowired
-    private ImplementationInfoProvider implementationInfoProvider;
+    protected ImplementationInfoProvider implementationInfoProvider;
 
     @Autowired
-    private ApplicationContext ctx;
+    protected ApplicationContext ctx;
 
-    private Map<String, Class<? extends BasePage>> pageClasses;
+    protected Map<String, Class<? extends BasePage>> pageClasses;
 
-    private String defaultAnyLayoutClass;
+    protected String defaultAnyLayoutClass;
 
     @SuppressWarnings("unchecked")
     protected void populatePageClasses(final Properties props) {
@@ -181,55 +190,6 @@ public class SyncopeWebApplication extends WicketBootSecuredWebApplication {
         // read console.properties
         Properties props = PropertyUtils.read(getClass(), CONSOLE_PROPERTIES, "console.directory");
 
-        anonymousUser = props.getProperty("anonymousUser");
-        Args.notNull(anonymousUser, "<anonymousUser>");
-        anonymousKey = props.getProperty("anonymousKey");
-        Args.notNull(anonymousKey, "<anonymousKey>");
-
-        useGZIPCompression = BooleanUtils.toBoolean(props.getProperty("useGZIPCompression"));
-        Args.notNull(useGZIPCompression, "<useGZIPCompression>");
-
-        try {
-            maxUploadFileSizeMB = props.getProperty("maxUploadFileSizeMB") == null
-                    ? null
-                    : Integer.valueOf(props.getProperty("maxUploadFileSizeMB"));
-        } catch (NumberFormatException e) {
-            LOG.error("Invalid value provided for 'maxUploadFileSizeMB': {}",
-                    props.getProperty("maxUploadFileSizeMB"));
-            maxUploadFileSizeMB = null;
-        }
-
-        try {
-            maxWaitTime = Integer.valueOf(props.getProperty("maxWaitTimeOnApplyChanges", "30"));
-        } catch (NumberFormatException e) {
-            LOG.error("Invalid value provided for 'maxWaitTimeOnApplyChanges': {}",
-                    props.getProperty("maxWaitTimeOnApplyChanges"));
-            maxWaitTime = 30;
-        }
-
-        // Resource connections check thread pool size
-        try {
-            corePoolSize = Integer.valueOf(props.getProperty("executor.corePoolSize", "5"));
-        } catch (NumberFormatException e) {
-            LOG.error("Invalid value provided for 'executor.corePoolSize': {}",
-                    props.getProperty("executor.corePoolSize"));
-            corePoolSize = 5;
-        }
-        try {
-            maxPoolSize = Integer.valueOf(props.getProperty("executor.maxPoolSize", "10"));
-        } catch (NumberFormatException e) {
-            LOG.error("Invalid value provided for 'executor.maxPoolSize': {}",
-                    props.getProperty("executor.maxPoolSize"));
-            maxPoolSize = 10;
-        }
-        try {
-            queueCapacity = Integer.valueOf(props.getProperty("executor.queueCapacity", "50"));
-        } catch (NumberFormatException e) {
-            LOG.error("Invalid value provided for 'executor.queueCapacity': {}",
-                    props.getProperty("executor.queueCapacity"));
-            maxPoolSize = 50;
-        }
-
         // process page properties
         pageClasses = new HashMap<>();
         populatePageClasses(props);
@@ -315,13 +275,6 @@ public class SyncopeWebApplication extends WicketBootSecuredWebApplication {
 
         mountPage("/login", getSignInPageClass());
 
-        try {
-            reconciliationReportKey = props.getProperty("reconciliationReportKey");
-        } catch (NumberFormatException e) {
-            LOG.error("While parsing reconciliationReportKey", e);
-        }
-        Args.notNull(reconciliationReportKey, "<reconciliationReportKey>");
-
         for (Class<? extends AbstractResource> resource : lookup.getClasses(AbstractResource.class)) {
             Resource annotation = resource.getAnnotation(Resource.class);
             try {
@@ -329,7 +282,7 @@ public class SyncopeWebApplication extends WicketBootSecuredWebApplication {
 
                 mountResource(annotation.path(), new ResourceReference(annotation.key()) {
 
-                    private static final long serialVersionUID = -128426276529456602L;
+                    protected static final long serialVersionUID = -128426276529456602L;
 
                     @Override
                     public IResource getResource() {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
index a6ff75f..94451e8 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
@@ -24,6 +24,7 @@ import java.time.Duration;
 import java.util.List;
 import java.util.stream.StreamSupport;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.SyncopeWebApplication;
@@ -40,7 +41,6 @@ import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
 import org.apache.syncope.client.ui.commons.rest.ResponseHolder;
 import org.apache.syncope.common.lib.SyncopeConstants;
-import org.apache.syncope.common.lib.info.PlatformInfo;
 import org.apache.syncope.common.lib.info.SystemInfo;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.wicket.AttributeModifier;
@@ -98,13 +98,13 @@ public class BasePage extends BaseWebPage {
         body.add(new Label("username", username));
 
         // right sidebar
-        PlatformInfo platformInfo = SyncopeConsoleSession.get().getPlatformInfo();
-        Label version = new Label("version", platformInfo.getVersion());
-        String versionLink = StringUtils.isNotBlank(platformInfo.getBuildNumber())
-                && platformInfo.getVersion().endsWith("-SNAPSHOT")
+        Pair<String, String> gitAndBuildInfo = SyncopeConsoleSession.get().gitAndBuildInfo();
+        Label version = new Label("version", gitAndBuildInfo.getRight());
+        String versionLink = StringUtils.isNotBlank(gitAndBuildInfo.getLeft())
+                && gitAndBuildInfo.getRight().endsWith("-SNAPSHOT")
                 ? "https://gitbox.apache.org/repos/asf?p=syncope.git;a=commit;h="
-                + platformInfo.getBuildNumber()
-                : "https://cwiki.apache.org/confluence/display/SYNCOPE/Fusion";
+                + gitAndBuildInfo.getLeft()
+                : "https://cwiki.apache.org/confluence/display/SYNCOPE/Maggiore";
         version.add(new AttributeModifier("onclick", "window.open('" + versionLink + "', '_blank')"));
         body.add(version);
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java
index 91dff7f..7f7e697 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java
@@ -58,7 +58,6 @@ public class AnyTypesPanel extends TypesDirectoryPanel<AnyTypeTO, AnyTypesPanel.
 
     public AnyTypesPanel(final String id, final PageReference pageRef) {
         super(id, false, pageRef);
-        this.restClient = new AnyTypeRestClient();
         disableCheckBoxes();
 
         this.addNewItemPanelBuilder(new AbstractModalPanelBuilder<AnyTypeTO>(new AnyTypeTO(), pageRef) {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardOverviewPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardOverviewPanel.java
index e503bcd..0483497 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardOverviewPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DashboardOverviewPanel.java
@@ -21,7 +21,7 @@ package org.apache.syncope.client.console.panels;
 import java.time.Duration;
 import java.time.temporal.ChronoUnit;
 import org.apache.commons.lang3.tuple.Triple;
-import org.apache.syncope.client.console.rest.SyncopeRestClient;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior;
 import org.apache.syncope.client.console.widgets.AnyByRealmWidget;
 import org.apache.syncope.client.console.widgets.CompletenessWidget;
@@ -57,7 +57,7 @@ public class DashboardOverviewPanel extends Panel {
     public DashboardOverviewPanel(final String id) {
         super(id);
 
-        NumbersInfo numbers = SyncopeRestClient.numbers();
+        NumbersInfo numbers = SyncopeConsoleSession.get().getAnonymousClient().numbers();
 
         WebMarkupContainer container = new WebMarkupContainer("container");
         container.setOutputMarkupId(true);
@@ -97,7 +97,7 @@ public class DashboardOverviewPanel extends Panel {
                 numbers.getAny2ByRealm());
         container.add(anyByRealm);
 
-        load = new LoadWidget("load", SyncopeRestClient.system());
+        load = new LoadWidget("load", SyncopeConsoleSession.get().getAnonymousClient().system());
         container.add(load);
 
         container.add(new IndicatorAjaxTimerBehavior(Duration.of(60, ChronoUnit.SECONDS)) {
@@ -106,7 +106,7 @@ public class DashboardOverviewPanel extends Panel {
 
             @Override
             protected void onTimer(final AjaxRequestTarget target) {
-                NumbersInfo numbers = SyncopeRestClient.numbers();
+                NumbersInfo numbers = SyncopeConsoleSession.get().getAnonymousClient().numbers();
 
                 if (totalUsers.refresh(numbers.getTotalUsers())) {
                     target.add(totalUsers);
@@ -143,7 +143,7 @@ public class DashboardOverviewPanel extends Panel {
                     target.add(anyByRealm);
                 }
 
-                load.refresh(SyncopeRestClient.system());
+                load.refresh(SyncopeConsoleSession.get().getAnonymousClient().system());
                 target.add(load);
             }
         });
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DelegationDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DelegationDirectoryPanel.java
index a900fb0..fea07cf 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DelegationDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DelegationDirectoryPanel.java
@@ -85,7 +85,7 @@ public class DelegationDirectoryPanel extends
 
     @Override
     protected Collection<ActionLink.ActionType> getBatches() {
-        return Collections.emptyList();
+        return List.of();
     }
 
     @Override
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java
index fc5110a..1da79f4 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java
@@ -42,6 +42,7 @@ import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
 import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
 import org.apache.syncope.common.lib.AnyOperations;
 import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.info.PlatformInfo;
 import org.apache.syncope.common.lib.request.UserUR;
 import org.apache.syncope.common.lib.to.AnyTypeClassTO;
 import org.apache.syncope.common.lib.to.UserTO;
@@ -190,9 +191,8 @@ public class UserDirectoryPanel extends AnyDirectoryPanel<UserTO, UserRestClient
             }, ActionType.PASSWORD_MANAGEMENT, IdRepoEntitlement.USER_UPDATE).
                     setRealms(realm, model.getObject().getDynRealms());
 
-            if (SyncopeConsoleSession.get().getPlatformInfo().isPwdResetAllowed()
-                    && !SyncopeConsoleSession.get().getPlatformInfo().isPwdResetRequiringSecurityQuestions()) {
-
+            PlatformInfo platformInfo = SyncopeConsoleSession.get().getAnonymousClient().platform();
+            if (platformInfo.isPwdResetAllowed() && !platformInfo.isPwdResetRequiringSecurityQuestions()) {
                 panel.add(new ActionLink<UserTO>() {
 
                     private static final long serialVersionUID = -7978723352517770644L;
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java
index a1bf0fa..8baa82d 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java
@@ -20,7 +20,6 @@ package org.apache.syncope.client.console.panels.search;
 
 import java.io.Serializable;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
@@ -220,7 +219,7 @@ public final class SearchUtils implements Serializable {
             final List<SearchClause> clauses,
             final AbstractFiqlSearchConditionBuilder<?, ?, ?> builder) {
 
-        return buildFIQL(clauses, builder, Collections.emptyMap(), NO_CUSTOM_CONDITION);
+        return buildFIQL(clauses, builder, Map.of(), NO_CUSTOM_CONDITION);
     }
 
     public static String buildFIQL(
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/AnyTypeRestClient.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/AnyTypeRestClient.java
index 912fb29..22fb73f 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/AnyTypeRestClient.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/AnyTypeRestClient.java
@@ -22,6 +22,7 @@ import java.io.Serializable;
 import java.util.Comparator;
 import java.util.List;
 import org.apache.commons.lang3.ObjectUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.AnyTypeTO;
@@ -60,7 +61,7 @@ public class AnyTypeRestClient extends BaseRestClient {
     }
 
     public static List<String> list() {
-        List<String> types = getSyncopeService().platform().getAnyTypes();
+        List<String> types = SyncopeConsoleSession.get().getAnonymousClient().platform().getAnyTypes();
         types.sort(new AnyTypeKeyComparator());
         return types;
     }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/RoleRestClient.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/RoleRestClient.java
index 5c53661..c0df666 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/RoleRestClient.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/RoleRestClient.java
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.common.lib.to.RoleTO;
 import org.apache.syncope.common.rest.api.service.RoleService;
 
@@ -75,6 +76,7 @@ public class RoleRestClient extends BaseRestClient {
     }
 
     public static List<String> getAllAvailableEntitlements() {
-        return getSyncopeService().platform().getEntitlements().stream().sorted().collect(Collectors.toList());
+        return SyncopeConsoleSession.get().getAnonymousClient().platform().getEntitlements().
+                stream().sorted().collect(Collectors.toList());
     }
 }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/SyncopeRestClient.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/SyncopeRestClient.java
index 89ade33..47f7ef0 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/SyncopeRestClient.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/SyncopeRestClient.java
@@ -20,8 +20,6 @@ package org.apache.syncope.client.console.rest;
 
 import javax.ws.rs.core.Response;
 import java.util.List;
-import org.apache.syncope.common.lib.info.NumbersInfo;
-import org.apache.syncope.common.lib.info.SystemInfo;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.rest.api.service.SyncopeService;
 
@@ -29,14 +27,6 @@ public class SyncopeRestClient extends BaseRestClient {
 
     private static final long serialVersionUID = -9013241672773442286L;
 
-    public static NumbersInfo numbers() {
-        return getService(SyncopeService.class).numbers();
-    }
-
-    public static SystemInfo system() {
-        return getService(SyncopeService.class).system();
-    }
-
     public static Response exportInternalStorageContent() {
         return getService(SyncopeService.class).exportInternalStorageContent();
     }
diff --git a/client/idrepo/console/src/main/resources/application.properties b/client/idrepo/console/src/main/resources/application.properties
index 9fa5bed..8f4f239 100644
--- a/client/idrepo/console/src/main/resources/application.properties
+++ b/client/idrepo/console/src/main/resources/application.properties
@@ -24,6 +24,9 @@ server.servlet.encoding.force=true
 
 server.servlet.contextPath=/syncope-console
 
+management.endpoints.web.exposure.include=info,health,loggers
+management.endpoint.health.show-details=ALWAYS
+
 service.discovery.address=http://localhost:8080/syncope-console/
 
 wicket.core.csrf.enabled=false
diff --git a/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java b/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java
index 6e12edb..6797501 100644
--- a/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java
+++ b/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java
@@ -34,7 +34,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Properties;
 import java.util.Set;
-import java.util.stream.Stream;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.commons.lang3.tuple.Triple;
 import org.apache.cxf.jaxrs.client.Client;
 import org.apache.syncope.client.console.AbstractTest.TestSyncopeWebApplication.SyncopeServiceClient;
@@ -64,11 +64,11 @@ import org.apache.syncope.client.ui.commons.MIMETypesLoader;
 import org.apache.syncope.common.keymaster.client.api.DomainOps;
 import org.apache.syncope.common.keymaster.client.api.ServiceOps;
 import org.apache.syncope.common.keymaster.client.api.model.Domain;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.info.NumbersInfo;
 import org.apache.syncope.common.lib.info.PlatformInfo;
 import org.apache.syncope.common.lib.info.SystemInfo;
-import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.AnyTypeTO;
 import org.apache.syncope.common.lib.to.PlainSchemaTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
@@ -203,14 +203,6 @@ public abstract class AbstractTest {
             when(service.type(anyString())).thenReturn(service);
             when(service.accept(anyString())).thenReturn(service);
 
-            when(service.platform()).thenReturn(new PlatformInfo());
-            when(service.system()).thenReturn(new SystemInfo());
-
-            NumbersInfo numbersInfo = new NumbersInfo();
-            Stream.of(NumbersInfo.ConfItem.values()).
-                    forEach(item -> numbersInfo.getConfCompleteness().put(item.name(), true));
-            when(service.numbers()).thenReturn(numbersInfo);
-
             return service;
         }
 
@@ -256,6 +248,34 @@ public abstract class AbstractTest {
 
             when(client.self()).thenReturn(Triple.of(new HashMap<>(), List.of(), getUserTO()));
 
+            when(client.gitAndBuildInfo()).thenReturn(Pair.of("", ""));
+            when(client.platform()).thenReturn(new PlatformInfo());
+            when(client.numbers()).thenAnswer(ic -> {
+                NumbersInfo numbersInfo = new NumbersInfo();
+
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.RESOURCE.name(), numbersInfo.getTotalResources() > 0);
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.ACCOUNT_POLICY.name(), false);
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.PASSWORD_POLICY.name(), false);
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.NOTIFICATION.name(), false);
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.PULL_TASK.name(), false);
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.VIR_SCHEMA.name(), false);
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.ANY_TYPE.name(), false);
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.SECURITY_QUESTION.name(), false);
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.ROLE.name(), numbersInfo.getTotalRoles() > 0);
+
+                return numbersInfo;
+            });
+            when(client.system()).thenReturn(new SystemInfo());
+
             SyncopeService syncopeService = getSyncopeService();
             when(client.getService(SyncopeService.class)).thenReturn(syncopeService);
 
diff --git a/client/idrepo/enduser/pom.xml b/client/idrepo/enduser/pom.xml
index 9bf1786..b1ca673 100644
--- a/client/idrepo/enduser/pom.xml
+++ b/client/idrepo/enduser/pom.xml
@@ -52,6 +52,14 @@ under the License.
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-log4j2</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-actuator</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-security</artifactId>
+    </dependency>
     
     <dependency>
       <groupId>org.apache.syncope.client.idrepo</groupId>
@@ -126,11 +134,6 @@ under the License.
   <build>
     <plugins>
       <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-checkstyle-plugin</artifactId>
-      </plugin>
-      
-      <plugin>
         <groupId>nl.geodienstencentrum.maven</groupId>
         <artifactId>sass-maven-plugin</artifactId>
         <inherited>true</inherited>
@@ -155,6 +158,30 @@ under the License.
           <skip>${sass.skip}</skip>
         </configuration>
       </plugin>
+
+      <plugin>
+        <groupId>io.github.git-commit-id</groupId>
+        <artifactId>git-commit-id-maven-plugin</artifactId>
+        <configuration>
+          <failOnNoGitDirectory>false</failOnNoGitDirectory>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>build-info</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+      </plugin>
     </plugins>
     
     <resources>
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SecurityConfig.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SecurityConfig.java
new file mode 100644
index 0000000..507bb68
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SecurityConfig.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.client.enduser;
+
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+@EnableWebSecurity
+@Configuration
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+    @Value("${anonymousUser}")
+    private String anonymousUser;
+
+    @Value("${anonymousKey}")
+    private String anonymousKey;
+
+    @Override
+    protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
+        auth.inMemoryAuthentication().
+                withUser(anonymousUser).
+                password("{noop}" + anonymousKey).
+                roles(IdRepoEntitlement.ANONYMOUS);
+    }
+
+    @Override
+    protected void configure(final HttpSecurity http) throws Exception {
+        http.csrf().disable().
+                authorizeRequests().
+                requestMatchers(EndpointRequest.toAnyEndpoint()).
+                authenticated().
+                and().
+                httpBasic();
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java
index 9760fe7..58556d7 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java
@@ -23,6 +23,7 @@ import org.apache.syncope.client.enduser.commons.PreviewUtils;
 import org.apache.syncope.client.enduser.init.ClassPathScanImplementationLookup;
 import org.apache.syncope.client.ui.commons.ApplicationContextProvider;
 import org.apache.syncope.client.ui.commons.MIMETypesLoader;
+import org.apache.syncope.client.ui.commons.actuate.SyncopeCoreHealthIndicator;
 import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
@@ -34,7 +35,10 @@ import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConf
 import org.springframework.boot.builder.SpringApplicationBuilder;
 import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.PropertySource;
 
+@PropertySource("classpath:enduser.properties")
+@PropertySource(value = "file:${console.directory}/enduser.properties", ignoreResourceNotFound = true)
 @SpringBootApplication(exclude = {
     ErrorMvcAutoConfiguration.class,
     HttpMessageConvertersAutoConfiguration.class })
@@ -51,18 +55,14 @@ public class SyncopeEnduserApplication extends SpringBootServletInitializer {
     }
 
     @Bean
-    public KeymasterStart keymasterStart() {
-        return new KeymasterStart(NetworkService.Type.ENDUSER);
-    }
-
-    @Bean
-    public KeymasterStop keymasterStop() {
-        return new KeymasterStop(NetworkService.Type.ENDUSER);
+    public ApplicationContextProvider applicationContextProvider() {
+        return new ApplicationContextProvider();
     }
 
+    @ConditionalOnMissingBean
     @Bean
-    public ApplicationContextProvider applicationContextProvider() {
-        return new ApplicationContextProvider();
+    public SyncopeCoreHealthIndicator syncopeCoreHealthIndicator() {
+        return new SyncopeCoreHealthIndicator();
     }
 
     @ConditionalOnMissingBean(name = "classPathScanImplementationLookup")
@@ -86,4 +86,14 @@ public class SyncopeEnduserApplication extends SpringBootServletInitializer {
     public PreviewUtils previewUtils() {
         return new PreviewUtils();
     }
+
+    @Bean
+    public KeymasterStart keymasterStart() {
+        return new KeymasterStart(NetworkService.Type.ENDUSER);
+    }
+
+    @Bean
+    public KeymasterStop keymasterStop() {
+        return new KeymasterStop(NetworkService.Type.ENDUSER);
+    }
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
index 81f8a25..cc15893 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
@@ -33,7 +33,6 @@ import org.apache.syncope.common.lib.info.SystemInfo;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.common.rest.api.RESTHeaders;
-import org.apache.syncope.common.rest.api.service.SyncopeService;
 import org.apache.wicket.Session;
 import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
 import org.apache.wicket.authroles.authorization.strategies.role.Roles;
@@ -119,8 +118,8 @@ public class SyncopeEnduserSession extends AuthenticatedWebSession implements Ba
                         SyncopeWebApplication.get().getAnonymousUser(),
                         SyncopeWebApplication.get().getAnonymousKey()));
 
-        platformInfo = getAnonymousService(SyncopeService.class).platform();
-        systemInfo = getAnonymousService(SyncopeService.class).system();
+        platformInfo = anonymousClient.platform();
+        systemInfo = anonymousClient.system();
 
         executor = new ThreadPoolTaskExecutor();
         executor.setWaitForTasksToCompleteOnShutdown(false);
@@ -177,10 +176,6 @@ public class SyncopeEnduserSession extends AuthenticatedWebSession implements Ba
         return clientFactory.getContentType().getMediaType();
     }
 
-    public SyncopeClient getAnonymousClient() {
-        return anonymousClient;
-    }
-
     public void execute(final Runnable command) {
         try {
             executor.execute(command);
@@ -320,6 +315,16 @@ public class SyncopeEnduserSession extends AuthenticatedWebSession implements Ba
         return selfTO;
     }
 
+    @Override
+    public SyncopeClient getAnonymousClient() {
+        return anonymousClient;
+    }
+
+    @Override
+    public <T> T getAnonymousService(final Class<T> serviceClass) {
+        return getService(serviceClass);
+    }
+
     @SuppressWarnings("unchecked")
     private <T> T getCachedService(final Class<T> serviceClass) {
         T service;
@@ -363,9 +368,4 @@ public class SyncopeEnduserSession extends AuthenticatedWebSession implements Ba
     public FastDateFormat getDateFormat() {
         return FastDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale());
     }
-
-    @Override
-    public <T> T getAnonymousService(final Class<T> serviceClass) {
-        return getAnonymousClient().getService(serviceClass);
-    }
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
index 37ca700..11e5e8a 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
@@ -76,10 +76,10 @@ import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.apache.wicket.request.resource.AbstractResource;
 import org.apache.wicket.request.resource.IResource;
 import org.apache.wicket.request.resource.ResourceReference;
-import org.apache.wicket.util.lang.Args;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
 @Component
@@ -106,25 +106,35 @@ public class SyncopeWebApplication extends WicketBootStandardWebApplication {
     @Autowired
     private ServiceOps serviceOps;
 
-    private boolean useGZIPCompression;
-
+    @Value("${adminUser}")
     private String adminUser;
 
-    private String anonymousUser;
+    @Value("${anonymousUser}")
+    protected String anonymousUser;
+
+    @Value("${anonymousKey}")
+    protected String anonymousKey;
 
-    private String anonymousKey;
+    @Value("${useGZIPCompression:false}")
+    protected boolean useGZIPCompression;
 
+    @Value("${captchaEnabled:false}")
     private boolean captchaEnabled;
 
-    private Integer maxWaitTime;
+    @Value("${maxUploadFileSizeMB:#{null}}")
+    protected Integer maxUploadFileSizeMB;
 
-    private Integer corePoolSize;
+    @Value("${maxWaitTime:30}")
+    protected Integer maxWaitTime;
 
-    private Integer maxPoolSize;
+    @Value("${corePoolSize:5}")
+    protected Integer corePoolSize;
 
-    private Integer queueCapacity;
+    @Value("${maxPoolSize:10}")
+    protected Integer maxPoolSize;
 
-    private Integer maxUploadFileSizeMB;
+    @Value("${queueCapacity:50}")
+    protected Integer queueCapacity;
 
     private FileAlterationMonitor customFormLayoutMonitor;
 
@@ -173,58 +183,6 @@ public class SyncopeWebApplication extends WicketBootStandardWebApplication {
         // read enduser.properties
         Properties props = PropertyUtils.read(getClass(), ENDUSER_PROPERTIES, "enduser.directory");
 
-        adminUser = props.getProperty("adminUser");
-        Args.notNull(adminUser, "<adminUser>");
-        anonymousUser = props.getProperty("anonymousUser");
-        Args.notNull(anonymousUser, "<anonymousUser>");
-        anonymousKey = props.getProperty("anonymousKey");
-        Args.notNull(anonymousKey, "<anonymousKey>");
-
-        captchaEnabled = Boolean.parseBoolean(props.getProperty("captcha"));
-        Args.notNull(captchaEnabled, "<captcha>");
-
-        useGZIPCompression = BooleanUtils.toBoolean(props.getProperty("useGZIPCompression"));
-        Args.notNull(useGZIPCompression, "<useGZIPCompression>");
-
-        try {
-            maxUploadFileSizeMB = Integer.valueOf(props.getProperty("maxUploadFileSizeMB", "4"));
-        } catch (NumberFormatException e) {
-            LOG.error("Invalid value provided for 'maxUploadFileSizeMB': {}",
-                    props.getProperty("maxUploadFileSizeMB"));
-            maxUploadFileSizeMB = 4;
-        }
-
-        try {
-            maxWaitTime = Integer.valueOf(props.getProperty("maxWaitTimeOnApplyChanges", "30"));
-        } catch (NumberFormatException e) {
-            LOG.error("Invalid value provided for 'maxWaitTimeOnApplyChanges': {}",
-                    props.getProperty("maxWaitTimeOnApplyChanges"));
-            maxWaitTime = 30;
-        }
-
-        // Resource connections check thread pool size
-        try {
-            corePoolSize = Integer.valueOf(props.getProperty("executor.corePoolSize", "5"));
-        } catch (NumberFormatException e) {
-            LOG.error("Invalid value provided for 'executor.corePoolSize': {}",
-                    props.getProperty("executor.corePoolSize"));
-            corePoolSize = 5;
-        }
-        try {
-            maxPoolSize = Integer.valueOf(props.getProperty("executor.maxPoolSize", "10"));
-        } catch (NumberFormatException e) {
-            LOG.error("Invalid value provided for 'executor.maxPoolSize': {}",
-                    props.getProperty("executor.maxPoolSize"));
-            maxPoolSize = 10;
-        }
-        try {
-            queueCapacity = Integer.valueOf(props.getProperty("executor.queueCapacity", "50"));
-        } catch (NumberFormatException e) {
-            LOG.error("Invalid value provided for 'executor.queueCapacity': {}",
-                    props.getProperty("executor.queueCapacity"));
-            maxPoolSize = 50;
-        }
-
         // read customFormLayout.json
         try (InputStream is = SyncopeWebApplication.class.getResourceAsStream('/' + CUSTOM_FORM_LAYOUT_FILE)) {
             customFormLayout = MAPPER.readValue(is, new TypeReference<UserFormLayoutInfo>() {
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/EditUser.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/EditUser.java
index 341f185..5d9b6d7 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/EditUser.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/EditUser.java
@@ -23,7 +23,6 @@ import org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import org.apache.syncope.client.enduser.layout.UserFormLayoutInfo;
 import org.apache.syncope.client.enduser.panels.UserFormPanel;
 import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.syncope.common.rest.api.service.SyncopeService;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 
@@ -48,7 +47,7 @@ public class EditUser extends BasePage {
                 "editUserPanel",
                 userTO,
                 userTO,
-                SyncopeEnduserSession.get().getService(SyncopeService.class).platform().getUserClasses(),
+                SyncopeEnduserSession.get().getAnonymousClient().platform().getUserClasses(),
                 buildFormLayout(),
                 getPageReference());
         editUserPanel.setOutputMarkupId(true);
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfRegistration.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfRegistration.java
index ec85d59..67096a3 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfRegistration.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfRegistration.java
@@ -26,7 +26,6 @@ import org.apache.syncope.client.enduser.layout.UserFormLayoutInfo;
 import org.apache.syncope.client.enduser.panels.UserSelfFormPanel;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.syncope.common.rest.api.service.SyncopeService;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 
@@ -54,7 +53,7 @@ public class SelfRegistration extends BasePage {
                 "selfRegistrationPanel",
                 buildNewUserTO(parameters),
                 buildNewUserTO(parameters),
-                SyncopeEnduserSession.get().getService(SyncopeService.class).platform().getUserClasses(),
+                SyncopeEnduserSession.get().getAnonymousClient().platform().getUserClasses(),
                 buildFormLayout(),
                 getPageReference());
         selfRegistrationPanel.setOutputMarkupId(true);
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/AnyFormPanel.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/AnyFormPanel.java
index ed9e79b..865cda3 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/AnyFormPanel.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/AnyFormPanel.java
@@ -73,7 +73,7 @@ public abstract class AnyFormPanel extends AbstractAnyFormPanel<UserWrapper> {
     }
 
     protected Details<UserTO> addOptionalDetailsPanel(final UserWrapper modelObject) {
-        Details<UserTO> details = new Details<>(EnduserConstants.CONTENT_PANEL, modelObject, false, true, pageRef);
+        Details<UserTO> details = new Details<>(EnduserConstants.CONTENT_PANEL, pageRef);
         details.setOutputMarkupId(true);
         return details;
     }
@@ -85,7 +85,7 @@ public abstract class AnyFormPanel extends AbstractAnyFormPanel<UserWrapper> {
                 .setComponent(addOptionalDetailsPanel(modelObject))
                 .isVisible(formLayoutInfo.isDetailsManagement()).build("userDetailsPanelCard"));
 
-        Groups groups = new Groups(EnduserConstants.CONTENT_PANEL, modelObject, false);
+        Groups groups = new Groups(EnduserConstants.CONTENT_PANEL, modelObject);
         setOutputMarkupId(true);
 
         form.add(new CardPanel.Builder<Groups>()
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserFormPanel.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserFormPanel.java
index 0a91fa7..0b5bb37 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserFormPanel.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserFormPanel.java
@@ -84,8 +84,6 @@ public class UserFormPanel extends AnyFormPanel implements UserForm {
         return new UserDetails(
                 EnduserConstants.CONTENT_PANEL,
                 UserWrapper.class.cast(modelObject),
-                false,
-                false,
                 pageRef);
     }
 
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserSelfFormPanel.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserSelfFormPanel.java
index a3a610d..9d1583e 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserSelfFormPanel.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserSelfFormPanel.java
@@ -72,8 +72,6 @@ public class UserSelfFormPanel extends UserFormPanel {
         return new SelfUserDetails(
                 EnduserConstants.CONTENT_PANEL,
                 UserWrapper.class.cast(modelObject),
-                false,
-                false,
                 UserFormLayoutInfo.class.cast(formLayoutInfo).isPasswordManagement(),
                 pageRef);
     }
@@ -101,7 +99,7 @@ public class UserSelfFormPanel extends UserFormPanel {
                         ? UserWrapper.class.cast(updatedWarapper).isStorePasswordInSyncope()
                         : StringUtils.isNotBlank(userTO.getPassword()));
 
-                result = userSelfRestClient.create(req, true);
+                result = userSelfRestClient.create(req);
                 LOG.debug("User {} has been created", result.getEntity().getUsername());
 
                 parameters.add(EnduserConstants.STATUS, Constants.OPERATION_SUCCEEDED);
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.java
index 9f6e4ec..0acd3bd 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.java
@@ -38,7 +38,6 @@ import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.util.ListModel;
 import java.io.Serializable;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -74,8 +73,8 @@ public abstract class AbstractAttrs<S extends SchemaTO> extends Panel {
             final Map<String, CustomizationOption> whichAttrs) {
         super(id);
         this.anyTypeClasses = anyTypeClasses;
-        this.attrs = new ListModel<>(Collections.emptyList());
-        this.membershipTOs = new ListModel<>(Collections.emptyList());
+        this.attrs = new ListModel<>(List.of());
+        this.membershipTOs = new ListModel<>(List.of());
 
         this.setOutputMarkupId(true);
 
@@ -102,8 +101,9 @@ public abstract class AbstractAttrs<S extends SchemaTO> extends Panel {
 
             for (MembershipTO membership : (List<MembershipTO>) PropertyResolver.getPropertyField(
                     "memberships", anyTO).get(anyTO)) {
-                setSchemas(Pair.of(membership.getGroupKey(), membership.getGroupName()), getMembershipAuxClasses(
-                        membership, anyTO.getType()));
+                setSchemas(Pair.of(
+                        membership.getGroupKey(), membership.getGroupName()),
+                        getMembershipAuxClasses(membership));
                 setAttrs(membership);
 
                 if (AbstractAttrs.this instanceof PlainAttrs && !membership.getPlainAttrs().isEmpty()) {
@@ -144,7 +144,7 @@ public abstract class AbstractAttrs<S extends SchemaTO> extends Panel {
                 : groupName + '#')
                 + schema;
         return whichAttrs.get(schemaName) == null
-                ? Collections.emptyList()
+                ? List.of()
                 : whichAttrs.get(schemaName).getDefaultValues();
     }
 
@@ -199,11 +199,11 @@ public abstract class AbstractAttrs<S extends SchemaTO> extends Panel {
 
     protected abstract List<Attr> getAttrsFromTO(MembershipTO membershipTO);
 
-    protected static List<String> getMembershipAuxClasses(final MembershipTO membershipTO, final String anyType) {
+    protected static List<String> getMembershipAuxClasses(final MembershipTO membershipTO) {
         try {
             return SyncopeRestClient.searchUserTypeExtensions(membershipTO.getGroupName());
         } catch (Exception e) {
-            return Collections.emptyList();
+            return List.of();
         }
     }
 
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/DerAttrs.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/DerAttrs.java
index fea60ce..f531228 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/DerAttrs.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/DerAttrs.java
@@ -19,7 +19,6 @@
 package org.apache.syncope.client.enduser.panels.any;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -37,7 +36,6 @@ import org.apache.syncope.common.lib.to.GroupableRelatableTO;
 import org.apache.syncope.common.lib.to.MembershipTO;
 import org.apache.syncope.common.lib.types.SchemaType;
 import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
-import org.apache.wicket.extensions.markup.html.tabs.ITab;
 import org.apache.wicket.markup.ComponentTag;
 import org.apache.wicket.markup.MarkupStream;
 import org.apache.wicket.markup.html.WebMarkupContainer;
@@ -69,8 +67,8 @@ public class DerAttrs extends AbstractAttrs<DerSchemaTO> {
 
             @Override
             protected void populateItem(final ListItem<MembershipTO> item) {
-                final MembershipTO membershipTO = item.getModelObject();
-                item.add(new Accordion("membershipDerSchemas", Collections.<ITab>singletonList(new AbstractTab(
+                MembershipTO membershipTO = item.getModelObject();
+                item.add(new Accordion("membershipDerSchemas", List.of(new AbstractTab(
                         new StringResourceModel(
                                 "attributes.membership.accordion",
                                 DerAttrs.this,
@@ -151,7 +149,7 @@ public class DerAttrs extends AbstractAttrs<DerSchemaTO> {
         membershipTO.getDerAttrs().addAll(derAttrs);
     }
 
-    public class DerSchemas extends Schemas {
+    public static class DerSchemas extends Schemas {
 
         private static final long serialVersionUID = -4730563859116024676L;
 
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Details.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Details.java
index 23c9bdf..1539c3b 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Details.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Details.java
@@ -18,7 +18,6 @@
  */
 package org.apache.syncope.client.enduser.panels.any;
 
-import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.markup.html.panel.Panel;
@@ -33,12 +32,7 @@ public class Details<T extends AnyTO> extends Panel {
 
     protected final PageReference pageRef;
 
-    public Details(
-            final String id,
-            final AnyWrapper<T> wrapper,
-            final boolean templateMode,
-            final boolean includeStatusPanel,
-            final PageReference pageRef) {
+    public Details(final String id, final PageReference pageRef) {
         super(id);
         this.pageRef = pageRef;
     }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Groups.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Groups.java
index 5b3912a..1ee0cd2 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Groups.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Groups.java
@@ -30,7 +30,6 @@ import org.apache.wicket.markup.html.form.IChoiceRenderer;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.util.ListModel;
 import java.io.Serializable;
-import java.util.Collections;
 import java.util.List;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -61,10 +60,7 @@ public class Groups extends Panel {
 
     protected WebMarkupContainer groupsContainer;
 
-    public <T extends AnyTO> Groups(final String id,
-            final AnyWrapper<T> modelObject,
-            final boolean templateMode) {
-
+    public <T extends AnyTO> Groups(final String id, final AnyWrapper<T> modelObject) {
         super(id);
         this.anyTO = modelObject.getInnerObject();
 
@@ -215,7 +211,7 @@ public class Groups extends Panel {
 
         @Override
         public List<String> getDynMemberships() {
-            return Collections.emptyList();
+            return List.of();
         }
 
         /**
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/PlainAttrs.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/PlainAttrs.java
index a25c76d..0ceea6f 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/PlainAttrs.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/PlainAttrs.java
@@ -19,7 +19,6 @@
 package org.apache.syncope.client.enduser.panels.any;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -39,7 +38,6 @@ import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.common.lib.types.SchemaType;
 import org.apache.wicket.AttributeModifier;
 import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
-import org.apache.wicket.extensions.markup.html.tabs.ITab;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.form.IChoiceRenderer;
@@ -94,8 +92,8 @@ public class PlainAttrs extends AbstractAttrs<PlainSchemaTO> {
 
             @Override
             protected void populateItem(final ListItem<MembershipTO> item) {
-                final MembershipTO membershipTO = item.getModelObject();
-                item.add(new Accordion("membershipPlainSchemas", Collections.<ITab>singletonList(new AbstractTab(
+                MembershipTO membershipTO = item.getModelObject();
+                item.add(new Accordion("membershipPlainSchemas", List.of(new AbstractTab(
                         new StringResourceModel(
                                 "attributes.membership.accordion",
                                 PlainAttrs.this,
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Resources.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Resources.java
index 93265e8..f02bcd6 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Resources.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Resources.java
@@ -27,7 +27,6 @@ import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
 import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
 import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
 import org.apache.syncope.common.lib.to.AnyTO;
-import org.apache.syncope.common.rest.api.service.SyncopeService;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.PropertyModel;
@@ -77,11 +76,11 @@ public class Resources extends Panel {
     @Override
     protected void onInitialize() {
         super.onInitialize();
-        available.setObject(SyncopeEnduserSession.get().getService(SyncopeService.class).platform().getResources());
+        available.setObject(SyncopeEnduserSession.get().getAnonymousClient().platform().getResources());
     }
 
     public boolean evaluate() {
-        available.setObject(SyncopeEnduserSession.get().getService(SyncopeService.class).platform().getResources());
+        available.setObject(SyncopeEnduserSession.get().getAnonymousClient().platform().getResources());
         return !available.getObject().isEmpty();
     }
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/SelfUserDetails.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/SelfUserDetails.java
index 3918934..e5515a9 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/SelfUserDetails.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/SelfUserDetails.java
@@ -28,17 +28,15 @@ public class SelfUserDetails extends UserDetails {
     public SelfUserDetails(
             final String id,
             final UserWrapper wrapper,
-            final boolean templateMode,
-            final boolean includeStatusPanel,
             final boolean showPasswordManagement,
             final PageReference pageRef) {
 
-        super(id, wrapper, templateMode, includeStatusPanel, pageRef);
+        super(id, wrapper, pageRef);
 
         // ------------------------
         // Password
         // ------------------------
-        EditUserPasswordPanel panel = new EditUserPasswordPanel("password", wrapper, templateMode);
+        EditUserPasswordPanel panel = new EditUserPasswordPanel("password", wrapper);
         panel.setVisible(showPasswordManagement);
 
         add(panel);
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/UserDetails.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/UserDetails.java
index 8897db7..fed52d4 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/UserDetails.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/UserDetails.java
@@ -47,15 +47,8 @@ public class UserDetails extends Details<UserTO> {
 
     protected final UserTO userTO;
 
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    public UserDetails(
-            final String id,
-            final UserWrapper wrapper,
-            final boolean templateMode,
-            final boolean includeStatusPanel,
-            final PageReference pageRef) {
-
-        super(id, wrapper, templateMode, includeStatusPanel, pageRef);
+    public UserDetails(final String id, final UserWrapper wrapper, final PageReference pageRef) {
+        super(id, pageRef);
 
         userTO = wrapper.getInnerObject();
         // ------------------------
@@ -68,11 +61,7 @@ public class UserDetails extends Details<UserTO> {
             username.showExternAction(new LabelInfo("externalAction", wrapper.getPreviousUserTO().getUsername()));
         }
 
-        if (templateMode) {
-            username.enableJexlHelp();
-        } else {
-            username.addRequiredLabel();
-        }
+        username.addRequiredLabel();
         add(username);
         // ------------------------
 
@@ -91,18 +80,14 @@ public class UserDetails extends Details<UserTO> {
 
         private static final long serialVersionUID = -8198836979773590078L;
 
-        protected EditUserPasswordPanel(
-                final String id,
-                final UserWrapper wrapper,
-                final boolean templateMode) {
-
+        protected EditUserPasswordPanel(final String id, final UserWrapper wrapper) {
             super(id);
             setOutputMarkupId(true);
             add(new Label("warning", new ResourceModel("password.change.warning")));
             add(new PasswordPanel(
                     "passwordPanel",
                     wrapper,
-                    templateMode,
+                    false,
                     wrapper.getInnerObject().getKey() == null,
                     new PasswordStrengthBehavior(new PasswordStrengthConfig().
                             withDebug(false).
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/VirAttrs.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/VirAttrs.java
index 65386d9..c9284e4 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/VirAttrs.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/VirAttrs.java
@@ -19,7 +19,6 @@
 package org.apache.syncope.client.enduser.panels.any;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -39,7 +38,6 @@ import org.apache.syncope.common.lib.to.MembershipTO;
 import org.apache.syncope.common.lib.to.VirSchemaTO;
 import org.apache.syncope.common.lib.types.SchemaType;
 import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
-import org.apache.wicket.extensions.markup.html.tabs.ITab;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.list.ListItem;
 import org.apache.wicket.markup.html.list.ListView;
@@ -69,22 +67,22 @@ public class VirAttrs extends AbstractAttrs<VirSchemaTO> {
 
             @Override
             protected void populateItem(final ListItem<MembershipTO> item) {
-                final MembershipTO membershipTO = item.getModelObject();
-                item.add(new Accordion("membershipVirSchemas",
-                        Collections.<ITab>singletonList(new AbstractTab(new StringResourceModel(
+                MembershipTO membershipTO = item.getModelObject();
+                item.add(new Accordion("membershipVirSchemas", List.of(
+                        new AbstractTab(new StringResourceModel(
                                 "attributes.membership.accordion", VirAttrs.this, Model.of(membershipTO))) {
 
-                            private static final long serialVersionUID = 1037272333056449378L;
-
-                            @Override
-                            public WebMarkupContainer getPanel(final String panelId) {
-                                return new VirAttrs.VirSchemas(
-                                        panelId,
-                                        membershipTO.getGroupName(),
-                                        membershipSchemas.get(membershipTO.getGroupKey()),
-                                        new ListModel<>(getAttrsFromTO(membershipTO)));
-                            }
-                        }), Model.of(-1)).setOutputMarkupId(true));
+                    private static final long serialVersionUID = 1037272333056449378L;
+
+                    @Override
+                    public WebMarkupContainer getPanel(final String panelId) {
+                        return new VirAttrs.VirSchemas(
+                                panelId,
+                                membershipTO.getGroupName(),
+                                membershipSchemas.get(membershipTO.getGroupKey()),
+                                new ListModel<>(getAttrsFromTO(membershipTO)));
+                    }
+                }), Model.of(-1)).setOutputMarkupId(true));
             }
         }).setOutputMarkupId(true);
     }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/AnyTypeRestClient.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/AnyTypeRestClient.java
index 09c6ea9..5045862 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/AnyTypeRestClient.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/AnyTypeRestClient.java
@@ -19,10 +19,10 @@
 package org.apache.syncope.client.enduser.rest;
 
 import java.io.Serializable;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import org.apache.commons.lang3.ObjectUtils;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.AnyTypeTO;
@@ -48,7 +48,7 @@ public class AnyTypeRestClient extends BaseRestClient {
     }
 
     public static List<AnyTypeTO> listAnyTypes() {
-        List<AnyTypeTO> types = Collections.emptyList();
+        List<AnyTypeTO> types = List.of();
 
         try {
             types = getService(AnyTypeService.class).list();
@@ -61,7 +61,7 @@ public class AnyTypeRestClient extends BaseRestClient {
     }
 
     public static List<String> list() {
-        List<String> types = getSyncopeService().platform().getAnyTypes();
+        List<String> types = SyncopeEnduserSession.get().getAnonymousClient().platform().getAnyTypes();
         types.sort(new AnyTypeKeyComparator());
         return types;
     }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SchemaRestClient.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SchemaRestClient.java
index 666b273..c89e35b 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SchemaRestClient.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SchemaRestClient.java
@@ -19,7 +19,6 @@
 package org.apache.syncope.client.enduser.rest;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
@@ -87,7 +86,7 @@ public class SchemaRestClient extends BaseRestClient {
     }
 
     public static List<String> getSchemaNames(final SchemaType schemaType) {
-        List<String> schemaNames = Collections.emptyList();
+        List<String> schemaNames = List.of();
 
         try {
             schemaNames = getSchemas(schemaType, null, new String[0]).stream().
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SyncopeRestClient.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SyncopeRestClient.java
index 233dd51..ec5b721 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SyncopeRestClient.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SyncopeRestClient.java
@@ -18,9 +18,7 @@
  */
 package org.apache.syncope.client.enduser.rest;
 
-import java.util.Collections;
 import java.util.List;
-import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.TypeExtensionTO;
 import org.apache.syncope.common.rest.api.service.SyncopeService;
 
@@ -28,19 +26,8 @@ public class SyncopeRestClient extends BaseRestClient {
 
     private static final long serialVersionUID = -2211371717449597247L;
 
-    public static List<String> listAnyTypeClasses() {
-        List<String> types = Collections.emptyList();
-
-        try {
-            types = getService(SyncopeService.class).platform().getAnyTypeClasses();
-        } catch (SyncopeClientException e) {
-            LOG.error("While reading all any type classes", e);
-        }
-        return types;
-    }
-
     public static List<String> searchUserTypeExtensions(final String groupName) {
-        List<String> types = Collections.emptyList();
+        List<String> types = List.of();
         try {
             TypeExtensionTO typeExtensionTO = getService(SyncopeService.class).readUserTypeExtension(groupName);
             types = typeExtensionTO == null ? types : typeExtensionTO.getAuxClasses();
@@ -49,5 +36,4 @@ public class SyncopeRestClient extends BaseRestClient {
         }
         return types;
     }
-
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserSelfRestClient.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserSelfRestClient.java
index f7cf9cf..d1a7cef 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserSelfRestClient.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserSelfRestClient.java
@@ -37,7 +37,7 @@ public class UserSelfRestClient extends BaseRestClient {
         getService(UserSelfService.class).requestPasswordReset(username, securityAnswer);
     }
 
-    public ProvisioningResult<UserTO> create(final UserCR createReq, final boolean storePassword) {
+    public ProvisioningResult<UserTO> create(final UserCR createReq) {
         ProvisioningResult<UserTO> result;
         result = getService(UserSelfService.class).create(createReq).readEntity(
                 new GenericType<ProvisioningResult<UserTO>>() {
diff --git a/client/idrepo/enduser/src/main/resources/application.properties b/client/idrepo/enduser/src/main/resources/application.properties
index 2cbc34e..719fd4b 100644
--- a/client/idrepo/enduser/src/main/resources/application.properties
+++ b/client/idrepo/enduser/src/main/resources/application.properties
@@ -24,6 +24,9 @@ server.servlet.encoding.force=true
 
 server.servlet.contextPath=/syncope-enduser
 
+management.endpoints.web.exposure.include=info,health,loggers
+management.endpoint.health.show-details=ALWAYS
+
 service.discovery.address=http://localhost:8080/syncope-enduser/
 
 wicket.core.csrf.enabled=false
diff --git a/client/idrepo/enduser/src/test/java/org/apache/syncope/client/enduser/AbstractTest.java b/client/idrepo/enduser/src/test/java/org/apache/syncope/client/enduser/AbstractTest.java
index c61be4e..14cdd07 100644
--- a/client/idrepo/enduser/src/test/java/org/apache/syncope/client/enduser/AbstractTest.java
+++ b/client/idrepo/enduser/src/test/java/org/apache/syncope/client/enduser/AbstractTest.java
@@ -33,7 +33,6 @@ import java.io.InputStream;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Properties;
-import java.util.stream.Stream;
 import org.apache.commons.lang3.tuple.Triple;
 import org.apache.cxf.jaxrs.client.Client;
 import org.apache.syncope.client.enduser.init.ClassPathScanImplementationLookup;
@@ -44,11 +43,11 @@ import org.apache.syncope.client.ui.commons.MIMETypesLoader;
 import org.apache.syncope.common.keymaster.client.api.DomainOps;
 import org.apache.syncope.common.keymaster.client.api.ServiceOps;
 import org.apache.syncope.common.keymaster.client.api.model.Domain;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.info.NumbersInfo;
 import org.apache.syncope.common.lib.info.PlatformInfo;
 import org.apache.syncope.common.lib.info.SystemInfo;
-import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.AnyTypeTO;
 import org.apache.syncope.common.lib.to.PlainSchemaTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
@@ -133,14 +132,6 @@ public abstract class AbstractTest {
             when(service.type(anyString())).thenReturn(service);
             when(service.accept(anyString())).thenReturn(service);
 
-            when(service.platform()).thenReturn(new PlatformInfo());
-            when(service.system()).thenReturn(new SystemInfo());
-
-            NumbersInfo numbersInfo = new NumbersInfo();
-            Stream.of(NumbersInfo.ConfItem.values()).
-                    forEach(item -> numbersInfo.getConfCompleteness().put(item.name(), true));
-            when(service.numbers()).thenReturn(numbersInfo);
-
             return service;
         }
 
@@ -187,6 +178,33 @@ public abstract class AbstractTest {
 
             when(client.self()).thenReturn(Triple.of(new HashMap<>(), List.of(), getUserTO()));
 
+            when(client.platform()).thenReturn(new PlatformInfo());
+            when(client.numbers()).thenAnswer(ic -> {
+                NumbersInfo numbersInfo = new NumbersInfo();
+
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.RESOURCE.name(), numbersInfo.getTotalResources() > 0);
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.ACCOUNT_POLICY.name(), false);
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.PASSWORD_POLICY.name(), false);
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.NOTIFICATION.name(), false);
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.PULL_TASK.name(), false);
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.VIR_SCHEMA.name(), false);
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.ANY_TYPE.name(), false);
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.SECURITY_QUESTION.name(), false);
+                numbersInfo.getConfCompleteness().put(
+                        NumbersInfo.ConfItem.ROLE.name(), numbersInfo.getTotalRoles() > 0);
+
+                return numbersInfo;
+            });
+            when(client.system()).thenReturn(new SystemInfo());
+
             SyncopeService syncopeService = getSyncopeService();
             when(client.getService(SyncopeService.class)).thenReturn(syncopeService);
 
diff --git a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java b/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
index 5d397e3..7c033fa 100644
--- a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
+++ b/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
@@ -20,16 +20,21 @@ package org.apache.syncope.client.lib;
 
 import org.apache.syncope.client.lib.batch.BatchRequest;
 import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import java.io.IOException;
+import java.io.InputStream;
+import java.util.Base64;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import javax.ws.rs.core.EntityTag;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.commons.lang3.tuple.Triple;
 import org.apache.cxf.configuration.jsse.TLSClientParameters;
 import org.apache.cxf.jaxrs.client.Client;
@@ -41,6 +46,9 @@ import org.apache.cxf.transport.common.gzip.GZIPOutInterceptor;
 import org.apache.cxf.transport.http.HTTPConduit;
 import org.apache.cxf.transport.http.URLConnectionHTTPConduit;
 import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.info.NumbersInfo;
+import org.apache.syncope.common.lib.info.PlatformInfo;
+import org.apache.syncope.common.lib.info.SystemInfo;
 import org.apache.syncope.common.lib.search.AnyObjectFiqlSearchConditionBuilder;
 import org.apache.syncope.common.lib.search.ConnObjectTOFiqlSearchConditionBuilder;
 import org.apache.syncope.common.lib.search.OrderByClauseBuilder;
@@ -60,19 +68,19 @@ import org.apache.syncope.common.rest.api.service.UserSelfService;
  */
 public class SyncopeClient {
 
-    private static final String HEADER_SPLIT_PROPERTY = "org.apache.cxf.http.header.split";
+    protected static final String HEADER_SPLIT_PROPERTY = "org.apache.cxf.http.header.split";
 
-    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+    protected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
 
-    private final MediaType mediaType;
+    protected final MediaType mediaType;
 
-    private final JAXRSClientFactoryBean restClientFactory;
+    protected final JAXRSClientFactoryBean restClientFactory;
 
-    private final RestClientExceptionMapper exceptionMapper;
+    protected final RestClientExceptionMapper exceptionMapper;
 
-    private final boolean useCompression;
+    protected final boolean useCompression;
 
-    private final TLSClientParameters tlsClientParameters;
+    protected final TLSClientParameters tlsClientParameters;
 
     public SyncopeClient(
             final MediaType mediaType,
@@ -136,6 +144,54 @@ public class SyncopeClient {
         restClientFactory.setPassword(null);
     }
 
+    protected JsonNode info() throws IOException {
+        WebClient webClient = WebClient.create(
+                restClientFactory.getAddress().replace("/rest", "/actuator/info")).
+                accept(MediaType.APPLICATION_JSON_TYPE).
+                header(RESTHeaders.DOMAIN, getDomain());
+
+        Optional.ofNullable(getJWT()).ifPresentOrElse(
+                jwt -> webClient.header(HttpHeaders.AUTHORIZATION, "Bearer " + jwt),
+                () -> webClient.header(HttpHeaders.AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString(
+                        (restClientFactory.getUsername() + ":" + restClientFactory.getPassword()).getBytes())));
+
+        return OBJECT_MAPPER.readTree((InputStream) webClient.get().getEntity());
+    }
+
+    public Pair<String, String> gitAndBuildInfo() {
+        try {
+            return Pair.of(
+                    info().get("git").get("commit").get("id").asText(),
+                    info().get("build").get("version").asText());
+        } catch (IOException e) {
+            throw new RuntimeException("While getting build and git Info", e);
+        }
+    }
+
+    public PlatformInfo platform() {
+        try {
+            return OBJECT_MAPPER.treeToValue(info().get("platform"), PlatformInfo.class);
+        } catch (IOException e) {
+            throw new RuntimeException("While getting Platform Info", e);
+        }
+    }
+
+    public SystemInfo system() {
+        try {
+            return OBJECT_MAPPER.treeToValue(info().get("system"), SystemInfo.class);
+        } catch (IOException e) {
+            throw new RuntimeException("While getting System Info", e);
+        }
+    }
+
+    public NumbersInfo numbers() {
+        try {
+            return OBJECT_MAPPER.treeToValue(info().get("numbers"), NumbersInfo.class);
+        } catch (IOException e) {
+            throw new RuntimeException("While getting Numbers Info", e);
+        }
+    }
+
     /**
      * Gives the base address for REST calls.
      *
@@ -373,7 +429,7 @@ public class SyncopeClient {
      * @param ifNot if true then {@code If-None-Match} is set, {@code If-Match} otherwise
      * @return given service instance, with {@code If-Match} or {@code If-None-Match} set
      */
-    private static <T> T match(final T service, final EntityTag etag, final boolean ifNot) {
+    protected static <T> T match(final T service, final EntityTag etag, final boolean ifNot) {
         WebClient.client(service).match(etag, ifNot);
         return service;
     }
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 380fd28..a58ee1d 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
@@ -227,10 +227,6 @@ public class PlatformInfo implements BaseBean {
         }
     }
 
-    private String version;
-
-    private String buildNumber;
-
     private String keymasterConfParamOps;
 
     private String keymasterServiceOps;
@@ -265,14 +261,6 @@ public class PlatformInfo implements BaseBean {
 
     private final Set<JavaImplInfo> javaImplInfos = new HashSet<>();
 
-    public String getVersion() {
-        return version;
-    }
-
-    public String getBuildNumber() {
-        return buildNumber;
-    }
-
     public String getKeymasterConfParamOps() {
         return keymasterConfParamOps;
     }
@@ -366,14 +354,6 @@ public class PlatformInfo implements BaseBean {
         return javaImplInfos;
     }
 
-    public void setVersion(final String version) {
-        this.version = version;
-    }
-
-    public void setBuildNumber(final String buildNumber) {
-        this.buildNumber = buildNumber;
-    }
-
     public void setKeymasterConfParamOps(final String keymasterConfParamOps) {
         this.keymasterConfParamOps = keymasterConfParamOps;
     }
diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SyncopeService.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SyncopeService.java
index 695dc04..83fac13 100644
--- a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SyncopeService.java
+++ b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SyncopeService.java
@@ -41,9 +41,6 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import org.apache.syncope.common.lib.info.NumbersInfo;
-import org.apache.syncope.common.lib.info.SystemInfo;
-import org.apache.syncope.common.lib.info.PlatformInfo;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.TypeExtensionTO;
@@ -60,38 +57,6 @@ import org.apache.syncope.common.rest.api.RESTHeaders;
 public interface SyncopeService extends JAXRSService {
 
     /**
-     * Provides information summary about platform configuration (workflow adapters, provisioning managers, validators,
-     * actions, correlation rules, reportlets, ...).
-     *
-     * @return information summary about platform configuration (workflow adapters, provisioning managers, validators,
-     * actions, correlation rules, reportlets, ...)
-     */
-    @GET
-    @Path("/platform")
-    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    PlatformInfo platform();
-
-    /**
-     * Provides information about the underlying system (Operating System, CPU / memory usage, ...).
-     *
-     * @return information about the underlying system (Operating System, CPU / memory usage, ...)
-     */
-    @GET
-    @Path("/system")
-    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    SystemInfo system();
-
-    /**
-     * Provides some numbers about the managed entities (users, groups, any objects...).
-     *
-     * @return some numbers about the managed entities (users, groups, any objects...)
-     */
-    @GET
-    @Path("/numbers")
-    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    NumbersInfo numbers();
-
-    /**
      * Requests for batch execution.
      *
      * @param input batch request
diff --git a/core/am/logic/pom.xml b/core/am/logic/pom.xml
index e4ad801..76d0077 100644
--- a/core/am/logic/pom.xml
+++ b/core/am/logic/pom.xml
@@ -61,24 +61,6 @@ under the License.
         
     <plugins>
       <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>buildnumber-maven-plugin</artifactId>
-        <inherited>true</inherited>
-        <configuration>
-          <doCheck>false</doCheck>
-          <doUpdate>false</doUpdate>
-        </configuration>
-        <executions>
-          <execution>
-            <phase>validate</phase>
-            <goals>
-              <goal>create</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-
-      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-checkstyle-plugin</artifactId>
       </plugin>
diff --git a/core/idm/logic/pom.xml b/core/idm/logic/pom.xml
index ccad8b4..9ea2854 100644
--- a/core/idm/logic/pom.xml
+++ b/core/idm/logic/pom.xml
@@ -119,24 +119,6 @@ under the License.
         
     <plugins>
       <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>buildnumber-maven-plugin</artifactId>
-        <inherited>true</inherited>
-        <configuration>
-          <doCheck>false</doCheck>
-          <doUpdate>false</doUpdate>
-        </configuration>
-        <executions>
-          <execution>
-            <phase>validate</phase>
-            <goals>
-              <goal>create</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-
-      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-checkstyle-plugin</artifactId>
       </plugin>
diff --git a/core/idrepo/logic/pom.xml b/core/idrepo/logic/pom.xml
index 3c2feee..6e43888 100644
--- a/core/idrepo/logic/pom.xml
+++ b/core/idrepo/logic/pom.xml
@@ -88,24 +88,6 @@ under the License.
         
     <plugins>
       <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>buildnumber-maven-plugin</artifactId>
-        <inherited>true</inherited>
-        <configuration>
-          <doCheck>false</doCheck>
-          <doUpdate>false</doUpdate>
-        </configuration>
-        <executions>
-          <execution>
-            <phase>validate</phase>
-            <goals>
-              <goal>create</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-
-      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-checkstyle-plugin</artifactId>
       </plugin>
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
index 60ae3a3..8a9b8bc 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
@@ -19,89 +19,37 @@
 package org.apache.syncope.core.logic;
 
 import java.io.OutputStream;
-import java.lang.management.ManagementFactory;
-import java.lang.management.OperatingSystemMXBean;
-import java.lang.management.RuntimeMXBean;
 import java.lang.reflect.Method;
-import java.net.InetAddress;
-import java.net.URI;
-import java.net.UnknownHostException;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import java.util.stream.Collectors;
-import javax.annotation.Resource;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
-import org.apache.syncope.common.keymaster.client.api.ServiceOps;
 import org.apache.syncope.common.lib.SyncopeConstants;
-import org.apache.syncope.common.lib.info.JavaImplInfo;
-import org.apache.syncope.common.lib.info.NumbersInfo;
-import org.apache.syncope.common.lib.info.SystemInfo;
-import org.apache.syncope.common.lib.info.PlatformInfo;
 import org.apache.syncope.common.lib.to.EntityTO;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.TypeExtensionTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
-import org.apache.syncope.common.lib.types.EntitlementsHolder;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
-import org.apache.syncope.common.lib.types.ImplementationTypesHolder;
-import org.apache.syncope.common.lib.types.TaskType;
-import org.apache.syncope.core.spring.security.PasswordGenerator;
-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.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.NotFoundException;
-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.dao.search.AnyCond;
 import org.apache.syncope.core.persistence.api.dao.search.AssignableCond;
 import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
-import org.apache.syncope.core.persistence.api.entity.AnyType;
-import org.apache.syncope.core.persistence.api.entity.Entity;
-import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.group.TypeExtension;
-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.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.data.GroupDataBinder;
-import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
-import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
 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.aop.support.AopUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.PayloadApplicationEvent;
-import org.springframework.context.event.EventListener;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
@@ -110,55 +58,13 @@ import org.springframework.transaction.annotation.Transactional;
 @Component
 public class SyncopeLogic extends AbstractLogic<EntityTO> {
 
-    private static final Pattern THREADPOOLTASKEXECUTOR_PATTERN = Pattern.compile(
-            ".*, pool size = ([0-9]+), "
-            + "active threads = ([0-9]+), "
-            + "queued tasks = ([0-9]+), "
-            + "completed tasks = ([0-9]+).*");
-
-    private static final Object MONITOR = new Object();
-
-    private static PlatformInfo PLATFORM_INFO;
-
-    private static SystemInfo SYSTEM_INFO;
-
     @Autowired
     private AnyTypeDAO anyTypeDAO;
 
     @Autowired
-    private AnyTypeClassDAO anyTypeClassDAO;
-
-    @Autowired
-    private UserDAO userDAO;
-
-    @Autowired
     private GroupDAO groupDAO;
 
     @Autowired
-    private AnyObjectDAO anyObjectDAO;
-
-    @Autowired
-    private ExternalResourceDAO resourceDAO;
-
-    @Autowired
-    private PolicyDAO policyDAO;
-
-    @Autowired
-    private NotificationDAO notificationDAO;
-
-    @Autowired
-    private TaskDAO taskDAO;
-
-    @Autowired
-    private VirSchemaDAO virSchemaDAO;
-
-    @Autowired
-    private RoleDAO roleDAO;
-
-    @Autowired
-    private SecurityQuestionDAO securityQuestionDAO;
-
-    @Autowired
     private AnySearchDAO searchDAO;
 
     @Autowired
@@ -168,21 +74,6 @@ public class SyncopeLogic extends AbstractLogic<EntityTO> {
     private ConfParamOps confParamOps;
 
     @Autowired
-    private ServiceOps serviceOps;
-
-    @Resource(name = "version")
-    private String version;
-
-    @Resource(name = "buildNumber")
-    private String buildNumber;
-
-    @Autowired
-    private ConnIdBundleManager bundleManager;
-
-    @Autowired
-    private PropagationTaskExecutor propagationTaskExecutor;
-
-    @Autowired
     private AnyObjectWorkflowAdapter awfAdapter;
 
     @Autowired
@@ -194,51 +85,6 @@ public class SyncopeLogic extends AbstractLogic<EntityTO> {
     @Autowired
     private GroupWorkflowAdapter gwfAdapter;
 
-    @Autowired
-    private AnyObjectProvisioningManager aProvisioningManager;
-
-    @Autowired
-    private UserProvisioningManager uProvisioningManager;
-
-    @Autowired
-    private GroupProvisioningManager gProvisioningManager;
-
-    @Autowired
-    private VirAttrCache virAttrCache;
-
-    @Autowired
-    private NotificationManager notificationManager;
-
-    @Autowired
-    private AuditManager auditManager;
-
-    @Autowired
-    private PasswordGenerator passwordGenerator;
-
-    @Autowired
-    private EntityFactory entityFactory;
-
-    @Autowired
-    private PlainSchemaDAO plainSchemaDAO;
-
-    @Autowired
-    private PlainAttrDAO plainAttrDAO;
-
-    @Autowired
-    private PlainAttrValueDAO plainAttrValueDAO;
-
-    @Autowired
-    private AnySearchDAO anySearchDAO;
-
-    @Autowired
-    private ImplementationLookup implLookup;
-
-    @Resource(name = "asyncConnectorFacadeExecutor")
-    private ThreadPoolTaskExecutor asyncConnectorFacadeExecutor;
-
-    @Resource(name = "propagationTaskExecutorAsyncExecutor")
-    private ThreadPoolTaskExecutor propagationTaskExecutorAsyncExecutor;
-
     public boolean isSelfRegAllowed() {
         return confParamOps.get(AuthContextUtils.getDomain(), "selfRegistration.allowed", false, Boolean.class);
     }
@@ -252,235 +98,6 @@ public class SyncopeLogic extends AbstractLogic<EntityTO> {
     }
 
     @PreAuthorize("isAuthenticated()")
-    public PlatformInfo platform() {
-        synchronized (MONITOR) {
-            if (PLATFORM_INFO == null) {
-                PLATFORM_INFO = new PlatformInfo();
-                PLATFORM_INFO.setVersion(version);
-                PLATFORM_INFO.setBuildNumber(buildNumber);
-                PLATFORM_INFO.setKeymasterConfParamOps(AopUtils.getTargetClass(confParamOps).getName());
-                PLATFORM_INFO.setKeymasterServiceOps(AopUtils.getTargetClass(serviceOps).getName());
-
-                if (bundleManager.getLocations() != null) {
-                    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) {
-                        JavaImplInfo javaImplInfo = new JavaImplInfo();
-                        javaImplInfo.setType(typeName);
-                        javaImplInfo.getClasses().addAll(classNames);
-
-                        PLATFORM_INFO.getJavaImplInfos().add(javaImplInfo);
-                    }
-                });
-            }
-
-            PLATFORM_INFO.setSelfRegAllowed(isSelfRegAllowed());
-            PLATFORM_INFO.setPwdResetAllowed(isPwdResetAllowed());
-            PLATFORM_INFO.setPwdResetRequiringSecurityQuestions(isPwdResetRequiringSecurityQuestions());
-
-            PLATFORM_INFO.getEntitlements().clear();
-            PLATFORM_INFO.getEntitlements().addAll(EntitlementsHolder.getInstance().getValues());
-
-            PLATFORM_INFO.getImplementationTypes().clear();
-            PLATFORM_INFO.getImplementationTypes().addAll(ImplementationTypesHolder.getInstance().getValues().keySet());
-
-            AuthContextUtils.callAsAdmin(AuthContextUtils.getDomain(), () -> {
-                PLATFORM_INFO.getAnyTypes().clear();
-                PLATFORM_INFO.getAnyTypes().addAll(anyTypeDAO.findAll().stream().
-                        map(Entity::getKey).collect(Collectors.toList()));
-
-                PLATFORM_INFO.getUserClasses().clear();
-                PLATFORM_INFO.getUserClasses().addAll(anyTypeDAO.findUser().getClasses().stream().
-                        map(Entity::getKey).collect(Collectors.toList()));
-
-                PLATFORM_INFO.getAnyTypeClasses().clear();
-                PLATFORM_INFO.getAnyTypeClasses().addAll(anyTypeClassDAO.findAll().stream().
-                        map(Entity::getKey).collect(Collectors.toList()));
-
-                PLATFORM_INFO.getResources().clear();
-                PLATFORM_INFO.getResources().addAll(resourceDAO.findAll().stream().
-                        map(Entity::getKey).collect(Collectors.toList()));
-                return null;
-            });
-        }
-
-        return PLATFORM_INFO;
-    }
-
-    private static void initSystemInfo() {
-        if (SYSTEM_INFO == null) {
-            OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
-            RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
-
-            SYSTEM_INFO = new SystemInfo();
-            try {
-                SYSTEM_INFO.setHostname(InetAddress.getLocalHost().getHostName());
-            } catch (UnknownHostException e) {
-                LOG.error("Could not get host name", e);
-            }
-
-            SYSTEM_INFO.setOs(operatingSystemMXBean.getName()
-                    + ' ' + operatingSystemMXBean.getVersion()
-                    + ' ' + operatingSystemMXBean.getArch());
-            SYSTEM_INFO.setAvailableProcessors(operatingSystemMXBean.getAvailableProcessors());
-            SYSTEM_INFO.setJvm(
-                    runtimeMXBean.getVmName()
-                    + ' ' + System.getProperty("java.version")
-                    + ' ' + runtimeMXBean.getVmVendor());
-            SYSTEM_INFO.setStartTime(runtimeMXBean.getStartTime());
-        }
-    }
-
-    @EventListener
-    public static void addLoadInstant(final PayloadApplicationEvent<SystemInfo.LoadInstant> event) {
-        synchronized (MONITOR) {
-            initSystemInfo();
-            SYSTEM_INFO.getLoad().add(event.getPayload());
-        }
-    }
-
-    @PreAuthorize("isAuthenticated()")
-    public static SystemInfo system() {
-        synchronized (MONITOR) {
-            initSystemInfo();
-        }
-
-        return SYSTEM_INFO;
-    }
-
-    private void setTaskExecutorInfo(final String toString, final NumbersInfo.TaskExecutorInfo info) {
-        Matcher matcher = THREADPOOLTASKEXECUTOR_PATTERN.matcher(toString);
-        if (matcher.matches() && matcher.groupCount() == 4) {
-            try {
-                info.setSize(Integer.valueOf(matcher.group(1)));
-            } catch (NumberFormatException e) {
-                LOG.error("While parsing thread pool size", e);
-            }
-            try {
-                info.setActive(Integer.valueOf(matcher.group(2)));
-            } catch (NumberFormatException e) {
-                LOG.error("While parsing active threads #", e);
-            }
-            try {
-                info.setQueued(Integer.valueOf(matcher.group(3)));
-            } catch (NumberFormatException e) {
-                LOG.error("While parsing queued threads #", e);
-            }
-            try {
-                info.setCompleted(Integer.valueOf(matcher.group(4)));
-            } catch (NumberFormatException e) {
-                LOG.error("While parsing completed threads #", e);
-            }
-        }
-    }
-
-    @PreAuthorize("isAuthenticated()")
-    public NumbersInfo numbers() {
-        NumbersInfo numbersInfo = new NumbersInfo();
-
-        numbersInfo.setTotalUsers(userDAO.count());
-        numbersInfo.getUsersByRealm().putAll(userDAO.countByRealm());
-        numbersInfo.getUsersByStatus().putAll(userDAO.countByStatus());
-
-        numbersInfo.setTotalGroups(groupDAO.count());
-        numbersInfo.getGroupsByRealm().putAll(groupDAO.countByRealm());
-
-        Map<AnyType, Integer> anyObjectNumbers = anyObjectDAO.countByType();
-        int i = 0;
-        for (Iterator<Map.Entry<AnyType, Integer>> itor = anyObjectNumbers.entrySet().iterator();
-                i < 2 && itor.hasNext(); i++) {
-
-            Map.Entry<AnyType, Integer> entry = itor.next();
-            if (i == 0) {
-                numbersInfo.setAnyType1(entry.getKey().getKey());
-                numbersInfo.setTotalAny1(entry.getValue());
-                numbersInfo.getAny1ByRealm().putAll(anyObjectDAO.countByRealm(entry.getKey()));
-            } else {
-                numbersInfo.setAnyType2(entry.getKey().getKey());
-                numbersInfo.setTotalAny2(entry.getValue());
-                numbersInfo.getAny2ByRealm().putAll(anyObjectDAO.countByRealm(entry.getKey()));
-            }
-        }
-
-        numbersInfo.setTotalResources(resourceDAO.count());
-
-        numbersInfo.setTotalRoles(roleDAO.count());
-
-        numbersInfo.getConfCompleteness().put(
-                NumbersInfo.ConfItem.RESOURCE.name(), numbersInfo.getTotalResources() > 0);
-        numbersInfo.getConfCompleteness().put(
-                NumbersInfo.ConfItem.ACCOUNT_POLICY.name(), !policyDAO.find(AccountPolicy.class).isEmpty());
-        numbersInfo.getConfCompleteness().put(
-                NumbersInfo.ConfItem.PASSWORD_POLICY.name(), !policyDAO.find(PasswordPolicy.class).isEmpty());
-        numbersInfo.getConfCompleteness().put(
-                NumbersInfo.ConfItem.NOTIFICATION.name(), !notificationDAO.findAll().isEmpty());
-        numbersInfo.getConfCompleteness().put(
-                NumbersInfo.ConfItem.PULL_TASK.name(), !taskDAO.findAll(TaskType.PULL).isEmpty());
-        numbersInfo.getConfCompleteness().put(
-                NumbersInfo.ConfItem.VIR_SCHEMA.name(), !virSchemaDAO.findAll().isEmpty());
-        numbersInfo.getConfCompleteness().put(
-                NumbersInfo.ConfItem.ANY_TYPE.name(), !anyObjectNumbers.isEmpty());
-        numbersInfo.getConfCompleteness().put(
-                NumbersInfo.ConfItem.SECURITY_QUESTION.name(), !securityQuestionDAO.findAll().isEmpty());
-        numbersInfo.getConfCompleteness().put(
-                NumbersInfo.ConfItem.ROLE.name(), numbersInfo.getTotalRoles() > 0);
-
-        setTaskExecutorInfo(
-                asyncConnectorFacadeExecutor.getThreadPoolExecutor().toString(),
-                numbersInfo.getAsyncConnectorExecutor());
-        setTaskExecutorInfo(
-                propagationTaskExecutorAsyncExecutor.getThreadPoolExecutor().toString(),
-                numbersInfo.getPropagationTaskExecutor());
-
-        return numbersInfo;
-    }
-
-    @PreAuthorize("isAuthenticated()")
     public Pair<Integer, List<GroupTO>> searchAssignableGroups(
             final String realm,
             final String term,
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SyncopeServiceImpl.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SyncopeServiceImpl.java
index a86f7e2..635c0c7 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SyncopeServiceImpl.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SyncopeServiceImpl.java
@@ -37,9 +37,6 @@ import org.apache.cxf.transport.http.DestinationRegistry;
 import org.apache.cxf.transport.http.HTTPTransportFactory;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.SyncopeConstants;
-import org.apache.syncope.common.lib.info.NumbersInfo;
-import org.apache.syncope.common.lib.info.SystemInfo;
-import org.apache.syncope.common.lib.info.PlatformInfo;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.to.TypeExtensionTO;
@@ -82,21 +79,6 @@ public class SyncopeServiceImpl extends AbstractServiceImpl implements SyncopeSe
     private EntityFactory entityFactory;
 
     @Override
-    public PlatformInfo platform() {
-        return logic.platform();
-    }
-
-    @Override
-    public SystemInfo system() {
-        return SyncopeLogic.system();
-    }
-
-    @Override
-    public NumbersInfo numbers() {
-        return logic.numbers();
-    }
-
-    @Override
     public PagedResult<GroupTO> searchAssignableGroups(
             final String realm, final String term, final int page, final int size) {
 
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAExternalResourceDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAExternalResourceDAO.java
index a76f49f..ca87a9d 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAExternalResourceDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAExternalResourceDAO.java
@@ -84,6 +84,7 @@ public class JPAExternalResourceDAO extends AbstractDAO<ExternalResource> implem
     @Autowired
     private RealmDAO realmDAO;
 
+    @Transactional(readOnly = true)
     @Override
     public int count() {
         Query query = entityManager().createQuery(
@@ -173,9 +174,10 @@ public class JPAExternalResourceDAO extends AbstractDAO<ExternalResource> implem
         return query.getResultList();
     }
 
+    @Transactional(readOnly = true)
     @Override
     public List<ExternalResource> findAll() {
-        final Set<String> authRealms = AuthContextUtils.getAuthorizations().get(IdMEntitlement.RESOURCE_LIST);
+        Set<String> authRealms = AuthContextUtils.getAuthorizations().get(IdMEntitlement.RESOURCE_LIST);
         if (authRealms == null || authRealms.isEmpty()) {
             return List.of();
         }
@@ -189,8 +191,8 @@ public class JPAExternalResourceDAO extends AbstractDAO<ExternalResource> implem
                 collect(Collectors.toList());
     }
 
-    @Override
     @Transactional(rollbackFor = { Throwable.class })
+    @Override
     public ExternalResource save(final ExternalResource resource) {
         ExternalResource merged = entityManager().merge(resource);
         try {
diff --git a/core/starter/pom.xml b/core/starter/pom.xml
index e094338..0cee5ae 100644
--- a/core/starter/pom.xml
+++ b/core/starter/pom.xml
@@ -52,7 +52,11 @@ under the License.
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-log4j2</artifactId>
     </dependency>
-    
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-actuator</artifactId>
+    </dependency>
+
     <dependency>
       <groupId>org.apache.syncope.core.idrepo</groupId>
       <artifactId>syncope-core-idrepo-rest-cxf</artifactId>
@@ -151,6 +155,25 @@ under the License.
       </plugin>
 
       <plugin>
+        <groupId>io.github.git-commit-id</groupId>
+        <artifactId>git-commit-id-maven-plugin</artifactId>
+        <configuration>
+          <failOnNoGitDirectory>false</failOnNoGitDirectory>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>build-info</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-checkstyle-plugin</artifactId>
       </plugin>
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 8902bec..83dcb4b 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
@@ -22,8 +22,14 @@ import java.io.IOException;
 import org.apache.cxf.spring.boot.autoconfigure.openapi.OpenApiAutoConfiguration;
 import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
+import org.apache.syncope.core.starter.actuate.DomainsHealthIndicator;
+import org.apache.syncope.core.starter.actuate.ExternalResourcesHealthIndicator;
+import org.apache.syncope.core.starter.actuate.SyncopeCoreInfoContributor;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.SpringApplication;
+import org.springframework.boot.actuate.mail.MailHealthIndicator;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@@ -34,6 +40,8 @@ import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConf
 import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.JavaMailSenderImpl;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
 
 @SpringBootApplication(exclude = {
@@ -60,6 +68,34 @@ public class SyncopeCoreApplication extends SpringBootServletInitializer {
         return pspc;
     }
 
+    @Autowired
+    protected JavaMailSender mailSender;
+
+    @ConditionalOnMissingBean
+    @Bean
+    public SyncopeCoreInfoContributor syncopeCoreInfoContributor() {
+        return new SyncopeCoreInfoContributor();
+    }
+
+    @ConditionalOnMissingBean
+    @Bean
+    public DomainsHealthIndicator domainsHealthIndicator() {
+        return new DomainsHealthIndicator();
+    }
+
+    @ConditionalOnMissingBean
+    @Bean
+    public MailHealthIndicator mailHealthIndicator() {
+        return new MailHealthIndicator((JavaMailSenderImpl) mailSender);
+    }
+
+    @ConditionalOnClass(name = { "org.apache.syncope.core.logic.ResourceLogic" })
+    @ConditionalOnMissingBean
+    @Bean
+    public ExternalResourcesHealthIndicator externalResourcesHealthIndicator() {
+        return new ExternalResourcesHealthIndicator();
+    }
+
     @Bean
     public SyncopeCoreStart keymasterStart() {
         return new SyncopeCoreStart();
diff --git a/core/starter/src/main/java/org/apache/syncope/core/starter/actuate/DomainsHealthIndicator.java b/core/starter/src/main/java/org/apache/syncope/core/starter/actuate/DomainsHealthIndicator.java
new file mode 100644
index 0000000..72c136c
--- /dev/null
+++ b/core/starter/src/main/java/org/apache/syncope/core/starter/actuate/DomainsHealthIndicator.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.starter.actuate;
+
+import java.sql.Connection;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.syncope.core.persistence.api.DomainHolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.boot.actuate.health.Status;
+import org.springframework.jdbc.datasource.DataSourceUtils;
+
+public class DomainsHealthIndicator implements HealthIndicator {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(DomainsHealthIndicator.class);
+
+    @Autowired
+    protected DomainHolder domainHolder;
+
+    @Override
+    public Health health() {
+        Health.Builder builder = new Health.Builder();
+
+        AtomicReference<Boolean> anyDown = new AtomicReference<>(Boolean.FALSE);
+
+        domainHolder.getDomains().forEach((key, ds) -> {
+            Status status;
+
+            Connection conn = null;
+            try {
+                conn = DataSourceUtils.getConnection(ds);
+                status = conn.isValid(0) ? Status.UP : Status.OUT_OF_SERVICE;
+            } catch (Exception e) {
+                status = Status.DOWN;
+                LOG.debug("When attempting to connect to Domain {}", key, e);
+            } finally {
+                if (conn != null) {
+                    DataSourceUtils.releaseConnection(conn, ds);
+                }
+            }
+
+            builder.withDetail(key, status);
+            if (status != Status.UP) {
+                anyDown.set(true);
+            }
+        });
+
+        builder.status(anyDown.get() ? Status.DOWN : Status.UP);
+
+        return builder.build();
+    }
+}
diff --git a/core/starter/src/main/java/org/apache/syncope/core/starter/actuate/ExternalResourcesHealthIndicator.java b/core/starter/src/main/java/org/apache/syncope/core/starter/actuate/ExternalResourcesHealthIndicator.java
new file mode 100644
index 0000000..b75da31
--- /dev/null
+++ b/core/starter/src/main/java/org/apache/syncope/core/starter/actuate/ExternalResourcesHealthIndicator.java
@@ -0,0 +1,91 @@
+/*
+ * 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.starter.actuate;
+
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Stream;
+import org.apache.syncope.common.keymaster.client.api.DomainOps;
+import org.apache.syncope.common.keymaster.client.api.model.Domain;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
+import org.apache.syncope.core.provisioning.api.ConnectorFactory;
+import org.apache.syncope.core.provisioning.api.data.ConnInstanceDataBinder;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.boot.actuate.health.Status;
+
+public class ExternalResourcesHealthIndicator implements HealthIndicator {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(ExternalResourcesHealthIndicator.class);
+
+    @Autowired
+    protected DomainOps domainOps;
+
+    @Autowired
+    protected ExternalResourceDAO resourceDAO;
+
+    @Autowired
+    protected ConnInstanceDataBinder connInstanceDataBinder;
+
+    @Autowired
+    protected ConnectorFactory connFactory;
+
+    @Override
+    public Health health() {
+        Health.Builder builder = new Health.Builder();
+
+        AtomicReference<Boolean> anyDown = new AtomicReference<>(Boolean.FALSE);
+
+        Stream.concat(Stream.of(SyncopeConstants.MASTER_DOMAIN), domainOps.list().stream().map(Domain::getKey)).
+                forEach(domain -> AuthContextUtils.callAsAdmin(domain, () -> {
+
+            resourceDAO.findAll().forEach(resource -> {
+                Status status;
+                try {
+                    connFactory.createConnector(
+                            connFactory.buildConnInstanceOverride(
+                                    connInstanceDataBinder.getConnInstanceTO(resource.getConnector()),
+                                    resource.getConfOverride(),
+                                    resource.isOverrideCapabilities()
+                                    ? Optional.of(resource.getCapabilitiesOverride()) : Optional.empty())).
+                            test();
+                    status = Status.UP;
+                } catch (Exception e) {
+                    status = Status.DOWN;
+                    LOG.debug("When checking {} in Domain {}", resource.getKey(), domain, e);
+                }
+
+                builder.withDetail(domain + "#" + resource.getKey(), status);
+                if (status != Status.UP) {
+                    anyDown.set(true);
+                }
+            });
+            return null;
+        }));
+
+        builder.status(anyDown.get() ? Status.DOWN : Status.UP);
+
+        return builder.build();
+    }
+}
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java b/core/starter/src/main/java/org/apache/syncope/core/starter/actuate/SyncopeCoreInfoContributor.java
similarity index 69%
copy from core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
copy to core/starter/src/main/java/org/apache/syncope/core/starter/actuate/SyncopeCoreInfoContributor.java
index 60ae3a3..467c302 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
+++ b/core/starter/src/main/java/org/apache/syncope/core/starter/actuate/SyncopeCoreInfoContributor.java
@@ -16,52 +16,39 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.logic;
+package org.apache.syncope.core.starter.actuate;
 
-import java.io.OutputStream;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import java.lang.management.ManagementFactory;
 import java.lang.management.OperatingSystemMXBean;
 import java.lang.management.RuntimeMXBean;
-import java.lang.reflect.Method;
 import java.net.InetAddress;
 import java.net.URI;
 import java.net.UnknownHostException;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import javax.annotation.Resource;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.syncope.common.keymaster.client.api.ServiceOps;
-import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.info.JavaImplInfo;
 import org.apache.syncope.common.lib.info.NumbersInfo;
-import org.apache.syncope.common.lib.info.SystemInfo;
 import org.apache.syncope.common.lib.info.PlatformInfo;
-import org.apache.syncope.common.lib.to.EntityTO;
-import org.apache.syncope.common.lib.to.GroupTO;
-import org.apache.syncope.common.lib.to.TypeExtensionTO;
-import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.info.SystemInfo;
 import org.apache.syncope.common.lib.types.EntitlementsHolder;
-import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.common.lib.types.ImplementationTypesHolder;
 import org.apache.syncope.common.lib.types.TaskType;
-import org.apache.syncope.core.spring.security.PasswordGenerator;
 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.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.NotFoundException;
 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;
@@ -72,16 +59,9 @@ 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.dao.search.AnyCond;
-import org.apache.syncope.core.persistence.api.dao.search.AssignableCond;
-import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
-import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
-import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
-import org.apache.syncope.core.persistence.api.entity.group.Group;
-import org.apache.syncope.core.persistence.api.entity.group.TypeExtension;
 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.provisioning.api.AnyObjectProvisioningManager;
@@ -90,174 +70,219 @@ 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.data.GroupDataBinder;
 import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
 import org.apache.syncope.core.spring.security.AuthContextUtils;
+import org.apache.syncope.core.spring.security.PasswordGenerator;
 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.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.info.Info;
+import org.springframework.boot.actuate.info.InfoContributor;
 import org.springframework.context.PayloadApplicationEvent;
 import org.springframework.context.event.EventListener;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 
-@Transactional(readOnly = true)
-@Component
-public class SyncopeLogic extends AbstractLogic<EntityTO> {
+public class SyncopeCoreInfoContributor implements InfoContributor {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(SyncopeCoreInfoContributor.class);
 
-    private static final Pattern THREADPOOLTASKEXECUTOR_PATTERN = Pattern.compile(
+    protected static final ObjectMapper MAPPER = new ObjectMapper().findAndRegisterModules();
+
+    protected static final Object MONITOR = new Object();
+
+    protected static PlatformInfo PLATFORM_INFO;
+
+    protected static SystemInfo SYSTEM_INFO;
+
+    protected static final Pattern THREADPOOLTASKEXECUTOR_PATTERN = Pattern.compile(
             ".*, pool size = ([0-9]+), "
             + "active threads = ([0-9]+), "
             + "queued tasks = ([0-9]+), "
             + "completed tasks = ([0-9]+).*");
 
-    private static final Object MONITOR = new Object();
-
-    private static PlatformInfo PLATFORM_INFO;
+    protected static void setTaskExecutorInfo(final String toString, final NumbersInfo.TaskExecutorInfo info) {
+        Matcher matcher = THREADPOOLTASKEXECUTOR_PATTERN.matcher(toString);
+        if (matcher.matches() && matcher.groupCount() == 4) {
+            try {
+                info.setSize(Integer.valueOf(matcher.group(1)));
+            } catch (NumberFormatException e) {
+                LOG.error("While parsing thread pool size", e);
+            }
+            try {
+                info.setActive(Integer.valueOf(matcher.group(2)));
+            } catch (NumberFormatException e) {
+                LOG.error("While parsing active threads #", e);
+            }
+            try {
+                info.setQueued(Integer.valueOf(matcher.group(3)));
+            } catch (NumberFormatException e) {
+                LOG.error("While parsing queued threads #", e);
+            }
+            try {
+                info.setCompleted(Integer.valueOf(matcher.group(4)));
+            } catch (NumberFormatException e) {
+                LOG.error("While parsing completed threads #", e);
+            }
+        }
+    }
 
-    private static SystemInfo SYSTEM_INFO;
+    protected static void initSystemInfo() {
+        if (SYSTEM_INFO == null) {
+            OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
+            RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
 
-    @Autowired
-    private AnyTypeDAO anyTypeDAO;
+            SYSTEM_INFO = new SystemInfo();
+            try {
+                SYSTEM_INFO.setHostname(InetAddress.getLocalHost().getHostName());
+            } catch (UnknownHostException e) {
+                LOG.error("Could not get host name", e);
+            }
 
-    @Autowired
-    private AnyTypeClassDAO anyTypeClassDAO;
+            SYSTEM_INFO.setOs(operatingSystemMXBean.getName()
+                    + ' ' + operatingSystemMXBean.getVersion()
+                    + ' ' + operatingSystemMXBean.getArch());
+            SYSTEM_INFO.setAvailableProcessors(operatingSystemMXBean.getAvailableProcessors());
+            SYSTEM_INFO.setJvm(
+                    runtimeMXBean.getVmName()
+                    + ' ' + System.getProperty("java.version")
+                    + ' ' + runtimeMXBean.getVmVendor());
+            SYSTEM_INFO.setStartTime(runtimeMXBean.getStartTime());
+        }
+    }
 
-    @Autowired
-    private UserDAO userDAO;
+    @EventListener
+    public void addLoadInstant(final PayloadApplicationEvent<SystemInfo.LoadInstant> event) {
+        synchronized (MONITOR) {
+            initSystemInfo();
+            SYSTEM_INFO.getLoad().add(event.getPayload());
+        }
+    }
 
     @Autowired
-    private GroupDAO groupDAO;
+    protected AnyTypeDAO anyTypeDAO;
 
     @Autowired
-    private AnyObjectDAO anyObjectDAO;
+    protected AnyTypeClassDAO anyTypeClassDAO;
 
     @Autowired
-    private ExternalResourceDAO resourceDAO;
+    protected UserDAO userDAO;
 
     @Autowired
-    private PolicyDAO policyDAO;
+    protected GroupDAO groupDAO;
 
     @Autowired
-    private NotificationDAO notificationDAO;
+    protected AnyObjectDAO anyObjectDAO;
 
     @Autowired
-    private TaskDAO taskDAO;
+    protected ExternalResourceDAO resourceDAO;
 
     @Autowired
-    private VirSchemaDAO virSchemaDAO;
+    protected ConfParamOps confParamOps;
 
     @Autowired
-    private RoleDAO roleDAO;
+    protected ServiceOps serviceOps;
 
     @Autowired
-    private SecurityQuestionDAO securityQuestionDAO;
+    protected ConnIdBundleManager bundleManager;
 
     @Autowired
-    private AnySearchDAO searchDAO;
+    protected PropagationTaskExecutor propagationTaskExecutor;
 
     @Autowired
-    private GroupDataBinder groupDataBinder;
+    protected AnyObjectWorkflowAdapter awfAdapter;
 
     @Autowired
-    private ConfParamOps confParamOps;
+    protected UserWorkflowAdapter uwfAdapter;
 
     @Autowired
-    private ServiceOps serviceOps;
-
-    @Resource(name = "version")
-    private String version;
-
-    @Resource(name = "buildNumber")
-    private String buildNumber;
+    protected GroupWorkflowAdapter gwfAdapter;
 
     @Autowired
-    private ConnIdBundleManager bundleManager;
+    protected AnyObjectProvisioningManager aProvisioningManager;
 
     @Autowired
-    private PropagationTaskExecutor propagationTaskExecutor;
+    protected UserProvisioningManager uProvisioningManager;
 
     @Autowired
-    private AnyObjectWorkflowAdapter awfAdapter;
+    protected GroupProvisioningManager gProvisioningManager;
 
     @Autowired
-    private ContentExporter exporter;
+    protected VirAttrCache virAttrCache;
 
     @Autowired
-    private UserWorkflowAdapter uwfAdapter;
+    protected NotificationManager notificationManager;
 
     @Autowired
-    private GroupWorkflowAdapter gwfAdapter;
+    protected AuditManager auditManager;
 
     @Autowired
-    private AnyObjectProvisioningManager aProvisioningManager;
+    protected PasswordGenerator passwordGenerator;
 
     @Autowired
-    private UserProvisioningManager uProvisioningManager;
+    protected EntityFactory entityFactory;
 
     @Autowired
-    private GroupProvisioningManager gProvisioningManager;
+    protected PlainSchemaDAO plainSchemaDAO;
 
     @Autowired
-    private VirAttrCache virAttrCache;
+    protected PlainAttrDAO plainAttrDAO;
 
     @Autowired
-    private NotificationManager notificationManager;
+    protected PlainAttrValueDAO plainAttrValueDAO;
 
     @Autowired
-    private AuditManager auditManager;
+    protected AnySearchDAO anySearchDAO;
 
     @Autowired
-    private PasswordGenerator passwordGenerator;
+    protected ImplementationLookup implLookup;
 
     @Autowired
-    private EntityFactory entityFactory;
+    protected PolicyDAO policyDAO;
 
     @Autowired
-    private PlainSchemaDAO plainSchemaDAO;
+    protected NotificationDAO notificationDAO;
 
     @Autowired
-    private PlainAttrDAO plainAttrDAO;
+    protected TaskDAO taskDAO;
 
     @Autowired
-    private PlainAttrValueDAO plainAttrValueDAO;
+    protected VirSchemaDAO virSchemaDAO;
 
     @Autowired
-    private AnySearchDAO anySearchDAO;
+    protected RoleDAO roleDAO;
 
     @Autowired
-    private ImplementationLookup implLookup;
+    protected SecurityQuestionDAO securityQuestionDAO;
 
     @Resource(name = "asyncConnectorFacadeExecutor")
-    private ThreadPoolTaskExecutor asyncConnectorFacadeExecutor;
+    protected ThreadPoolTaskExecutor asyncConnectorFacadeExecutor;
 
     @Resource(name = "propagationTaskExecutorAsyncExecutor")
-    private ThreadPoolTaskExecutor propagationTaskExecutorAsyncExecutor;
+    protected ThreadPoolTaskExecutor propagationTaskExecutorAsyncExecutor;
 
-    public boolean isSelfRegAllowed() {
+    protected boolean isSelfRegAllowed() {
         return confParamOps.get(AuthContextUtils.getDomain(), "selfRegistration.allowed", false, Boolean.class);
     }
 
-    public boolean isPwdResetAllowed() {
+    protected boolean isPwdResetAllowed() {
         return confParamOps.get(AuthContextUtils.getDomain(), "passwordReset.allowed", false, Boolean.class);
     }
 
-    public boolean isPwdResetRequiringSecurityQuestions() {
+    protected boolean isPwdResetRequiringSecurityQuestions() {
         return confParamOps.get(AuthContextUtils.getDomain(), "passwordReset.securityQuestion", true, Boolean.class);
     }
 
-    @PreAuthorize("isAuthenticated()")
-    public PlatformInfo platform() {
-        synchronized (MONITOR) {
+    protected void buildPlatform() {
+        synchronized (this) {
             if (PLATFORM_INFO == null) {
                 PLATFORM_INFO = new PlatformInfo();
-                PLATFORM_INFO.setVersion(version);
-                PLATFORM_INFO.setBuildNumber(buildNumber);
                 PLATFORM_INFO.setKeymasterConfParamOps(AopUtils.getTargetClass(confParamOps).getName());
                 PLATFORM_INFO.setKeymasterServiceOps(AopUtils.getTargetClass(serviceOps).getName());
 
@@ -348,79 +373,9 @@ public class SyncopeLogic extends AbstractLogic<EntityTO> {
                 return null;
             });
         }
-
-        return PLATFORM_INFO;
-    }
-
-    private static void initSystemInfo() {
-        if (SYSTEM_INFO == null) {
-            OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
-            RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
-
-            SYSTEM_INFO = new SystemInfo();
-            try {
-                SYSTEM_INFO.setHostname(InetAddress.getLocalHost().getHostName());
-            } catch (UnknownHostException e) {
-                LOG.error("Could not get host name", e);
-            }
-
-            SYSTEM_INFO.setOs(operatingSystemMXBean.getName()
-                    + ' ' + operatingSystemMXBean.getVersion()
-                    + ' ' + operatingSystemMXBean.getArch());
-            SYSTEM_INFO.setAvailableProcessors(operatingSystemMXBean.getAvailableProcessors());
-            SYSTEM_INFO.setJvm(
-                    runtimeMXBean.getVmName()
-                    + ' ' + System.getProperty("java.version")
-                    + ' ' + runtimeMXBean.getVmVendor());
-            SYSTEM_INFO.setStartTime(runtimeMXBean.getStartTime());
-        }
     }
 
-    @EventListener
-    public static void addLoadInstant(final PayloadApplicationEvent<SystemInfo.LoadInstant> event) {
-        synchronized (MONITOR) {
-            initSystemInfo();
-            SYSTEM_INFO.getLoad().add(event.getPayload());
-        }
-    }
-
-    @PreAuthorize("isAuthenticated()")
-    public static SystemInfo system() {
-        synchronized (MONITOR) {
-            initSystemInfo();
-        }
-
-        return SYSTEM_INFO;
-    }
-
-    private void setTaskExecutorInfo(final String toString, final NumbersInfo.TaskExecutorInfo info) {
-        Matcher matcher = THREADPOOLTASKEXECUTOR_PATTERN.matcher(toString);
-        if (matcher.matches() && matcher.groupCount() == 4) {
-            try {
-                info.setSize(Integer.valueOf(matcher.group(1)));
-            } catch (NumberFormatException e) {
-                LOG.error("While parsing thread pool size", e);
-            }
-            try {
-                info.setActive(Integer.valueOf(matcher.group(2)));
-            } catch (NumberFormatException e) {
-                LOG.error("While parsing active threads #", e);
-            }
-            try {
-                info.setQueued(Integer.valueOf(matcher.group(3)));
-            } catch (NumberFormatException e) {
-                LOG.error("While parsing queued threads #", e);
-            }
-            try {
-                info.setCompleted(Integer.valueOf(matcher.group(4)));
-            } catch (NumberFormatException e) {
-                LOG.error("While parsing completed threads #", e);
-            }
-        }
-    }
-
-    @PreAuthorize("isAuthenticated()")
-    public NumbersInfo numbers() {
+    protected NumbersInfo buildNumbers() {
         NumbersInfo numbersInfo = new NumbersInfo();
 
         numbersInfo.setTotalUsers(userDAO.count());
@@ -480,86 +435,31 @@ public class SyncopeLogic extends AbstractLogic<EntityTO> {
         return numbersInfo;
     }
 
-    @PreAuthorize("isAuthenticated()")
-    public Pair<Integer, List<GroupTO>> searchAssignableGroups(
-            final String realm,
-            final String term,
-            final int page,
-            final int size) {
-
-        AssignableCond assignableCond = new AssignableCond();
-        assignableCond.setRealmFullPath(realm);
-
-        SearchCond searchCond;
-        if (StringUtils.isNotBlank(term)) {
-            AnyCond termCond = new AnyCond(AttrCond.Type.ILIKE);
-            termCond.setSchema("name");
-
-            String termSearchableValue = (term.startsWith("*") && !term.endsWith("*"))
-                    ? term + '%'
-                    : (!term.startsWith("*") && term.endsWith("*"))
-                    ? '%' + term
-                    : (term.startsWith("*") && term.endsWith("*")
-                    ? term : '%' + term + '%');
-            termCond.setExpression(termSearchableValue);
-
-            searchCond = SearchCond.getAnd(
-                    SearchCond.getLeaf(assignableCond),
-                    SearchCond.getLeaf(termCond));
-        } else {
-            searchCond = SearchCond.getLeaf(assignableCond);
+    protected void buildSystem() {
+        synchronized (MONITOR) {
+            initSystemInfo();
         }
-
-        int count = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, searchCond, AnyTypeKind.GROUP);
-
-        OrderByClause orderByClause = new OrderByClause();
-        orderByClause.setField("name");
-        orderByClause.setDirection(OrderByClause.Direction.ASC);
-        List<Group> matching = searchDAO.search(
-                SyncopeConstants.FULL_ADMIN_REALMS,
-                searchCond,
-                page, size,
-                List.of(orderByClause), AnyTypeKind.GROUP);
-        List<GroupTO> result = matching.stream().
-                map(group -> groupDataBinder.getGroupTO(group, false)).collect(Collectors.toList());
-
-        return Pair.of(count, result);
     }
 
     @PreAuthorize("isAuthenticated()")
-    public TypeExtensionTO readTypeExtension(final String groupName) {
-        Group group = groupDAO.findByName(groupName);
-        if (group == null) {
-            throw new NotFoundException("Group " + groupName);
-        }
-        Optional<? extends TypeExtension> typeExt = group.getTypeExtension(anyTypeDAO.findUser());
-        if (typeExt.isEmpty()) {
-            throw new NotFoundException("TypeExtension in " + groupName + " for users");
-        }
-
-        return groupDataBinder.getTypeExtensionTO(typeExt.get());
-    }
-
-    @PreAuthorize("hasRole('" + IdRepoEntitlement.KEYMASTER + "')")
     @Transactional(readOnly = true)
-    public void exportInternalStorageContent(final OutputStream os) {
-        try {
-            exporter.export(
-                    AuthContextUtils.getDomain(),
-                    os,
-                    uwfAdapter.getPrefix(),
-                    gwfAdapter.getPrefix(),
-                    awfAdapter.getPrefix());
-            LOG.debug("Internal storage content successfully exported");
-        } catch (Exception e) {
-            LOG.error("While exporting internal storage content", e);
-        }
-    }
-
     @Override
-    protected EntityTO resolveReference(final Method method, final Object... args)
-            throws UnresolvedReferenceException {
-
-        throw new UnresolvedReferenceException();
+    public void contribute(final Info.Builder builder) {
+        buildPlatform();
+        builder.withDetail(
+                "platform",
+                MAPPER.convertValue(PLATFORM_INFO, new TypeReference<Map<String, Object>>() {
+                }));
+
+        builder.withDetail(
+                "numbers",
+                MAPPER.convertValue(buildNumbers(), new TypeReference<Map<String, Object>>() {
+                }));
+
+        buildSystem();
+        builder.withDetail(
+                "system",
+                MAPPER.convertValue(SYSTEM_INFO, new TypeReference<Map<String, Object>>() {
+                }));
     }
 }
diff --git a/core/starter/src/main/resources/application.properties b/core/starter/src/main/resources/application.properties
index ecd94ea..0363a8d 100644
--- a/core/starter/src/main/resources/application.properties
+++ b/core/starter/src/main/resources/application.properties
@@ -27,4 +27,7 @@ conf.directory=${conf.directory}
 server.servlet.contextPath=/syncope
 cxf.path=/rest
 
+management.endpoints.web.exposure.include=health,info,loggers
+management.endpoint.health.show-details=ALWAYS
+
 service.discovery.address=http://localhost:8080/syncope/rest/
diff --git a/docker/console/src/main/resources/application.properties b/docker/console/src/main/resources/application.properties
index b354e62..f50ca0c 100644
--- a/docker/console/src/main/resources/application.properties
+++ b/docker/console/src/main/resources/application.properties
@@ -24,6 +24,9 @@ server.servlet.encoding.force=true
 
 server.servlet.contextPath=/syncope-console
 
+management.endpoints.web.exposure.include=info,health,loggers
+management.endpoint.health.show-details=ALWAYS
+
 service.discovery.address=${SERVICE_DISCOVERY_ADDRESS}
 
 wicket.core.csrf.enabled=false
diff --git a/docker/core/src/main/resources/application.properties b/docker/core/src/main/resources/application.properties
index 493f5f0..6cb0197 100644
--- a/docker/core/src/main/resources/application.properties
+++ b/docker/core/src/main/resources/application.properties
@@ -27,4 +27,7 @@ conf.directory=${conf.directory}
 server.servlet.contextPath=/syncope
 cxf.path=/rest
 
+management.endpoints.web.exposure.include=health,info,loggers
+management.endpoint.health.show-details=ALWAYS
+
 service.discovery.address=${SERVICE_DISCOVERY_ADDRESS}
diff --git a/docker/enduser/src/main/resources/application.properties b/docker/enduser/src/main/resources/application.properties
index b5fb284..2b3c7b2 100644
--- a/docker/enduser/src/main/resources/application.properties
+++ b/docker/enduser/src/main/resources/application.properties
@@ -24,6 +24,9 @@ server.servlet.encoding.force=true
 
 server.servlet.contextPath=/syncope-enduser
 
+management.endpoints.web.exposure.include=info,health,loggers
+management.endpoint.health.show-details=ALWAYS
+
 service.discovery.address=${SERVICE_DISCOVERY_ADDRESS}
 
 wicket.core.csrf.enabled=false
diff --git a/docker/sra/src/main/resources/application.properties b/docker/sra/src/main/resources/application.properties
index 52c7a72..2e2da44 100644
--- a/docker/sra/src/main/resources/application.properties
+++ b/docker/sra/src/main/resources/application.properties
@@ -20,8 +20,9 @@ spring.main.banner-mode=log
 
 server.port=8080
 
-management.security.enabled=true
 management.endpoint.gateway.enabled=true
-management.endpoints.web.exposure.include=health,loggers,metrics,gateway,sraSessions
+management.endpoints.web.exposure.include=info,health,loggers,metrics,gateway,sraSessions
+management.endpoint.health.show-details=ALWAYS
+spring.cloud.discovery.client.health-indicator.enabled=false
 
 service.discovery.address=${SERVICE_DISCOVERY_ADDRESS}
diff --git a/docker/wa/src/main/resources/application.properties b/docker/wa/src/main/resources/application.properties
index b0d2e59..8446992 100644
--- a/docker/wa/src/main/resources/application.properties
+++ b/docker/wa/src/main/resources/application.properties
@@ -34,7 +34,9 @@ spring.web.resources.static-locations=classpath:/thymeleaf/static,classpath:/syn
 
 cas.monitor.endpoints.endpoint.defaults.access=AUTHENTICATED
 management.endpoints.enabled-by-default=true
-management.endpoints.web.exposure.include=*
+management.endpoints.web.exposure.include=info,health,loggers,ssoSessions
+management.endpoint.health.show-details=ALWAYS
+spring.cloud.discovery.client.health-indicator.enabled=false
 
 # Cache service definitions for 5 minutes
 cas.service-registry.cache.duration=PT5M
diff --git a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserRequestRestClient.java b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserRequestRestClient.java
index 93a5670..8f31e56 100644
--- a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserRequestRestClient.java
+++ b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserRequestRestClient.java
@@ -49,11 +49,12 @@ public class UserRequestRestClient extends BaseRestClient {
             final int size,
             final String username,
             final SortParam<String> sort) {
+
         return getService(UserRequestService.class).listRequests(new UserRequestQuery.Builder().
                 user(StringUtils.isBlank(username)
                         ? SyncopeEnduserSession.get().getSelfTO().getUsername()
                         : username).
-                page(page).size(size).build()).getResult();
+                page(page).size(size).orderBy(toOrderBy(sort)).build()).getResult();
     }
 
     public static void cancelRequest(final String executionId, final String reason) {
diff --git a/fit/core-reference/pom.xml b/fit/core-reference/pom.xml
index c10796d..983c546 100644
--- a/fit/core-reference/pom.xml
+++ b/fit/core-reference/pom.xml
@@ -217,24 +217,6 @@ under the License.
   <build>
     <plugins>
       <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>buildnumber-maven-plugin</artifactId>
-        <inherited>true</inherited>
-        <configuration>
-          <doCheck>false</doCheck>
-          <doUpdate>false</doUpdate>
-        </configuration>
-        <executions>
-          <execution>
-            <phase>validate</phase>
-            <goals>
-              <goal>create</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-
-      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-dependency-plugin</artifactId>
         <inherited>true</inherited>
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 ef208bc..1f84ccd 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
@@ -440,18 +440,6 @@ public abstract class AbstractITCase {
         impersonationService = anonymusClient.getService(ImpersonationService.class);
     }
 
-    @Autowired
-    protected ConfParamOps confParamOps;
-
-    @Autowired
-    protected ServiceOps serviceOps;
-
-    @Autowired
-    protected DomainOps domainOps;
-
-    @Autowired
-    protected DataSource testDataSource;
-
     protected static String getUUIDString() {
         return UUID.randomUUID().toString().substring(0, 8);
     }
@@ -464,7 +452,7 @@ public abstract class AbstractITCase {
         return new AttrPatch.Builder(attr(schema, value)).operation(PatchOperation.ADD_REPLACE).build();
     }
 
-    public static <T> T getObject(final URI location, final Class<?> serviceClass, final Class<T> resultClass) {
+    protected static <T> T getObject(final URI location, final Class<?> serviceClass, final Class<T> resultClass) {
         WebClient webClient = WebClient.fromClient(WebClient.client(adminClient.getService(serviceClass)));
         webClient.accept(clientFactory.getContentType().getMediaType()).to(location.toASCIIString(), false);
 
@@ -474,6 +462,18 @@ public abstract class AbstractITCase {
                 get(resultClass);
     }
 
+    @Autowired
+    protected ConfParamOps confParamOps;
+
+    @Autowired
+    protected ServiceOps serviceOps;
+
+    @Autowired
+    protected DomainOps domainOps;
+
+    @Autowired
+    protected DataSource testDataSource;
+
     @SuppressWarnings("unchecked")
     protected <T extends SchemaTO> T createSchema(final SchemaType type, final T schemaTO) {
         Response response = schemaService.create(type, schemaTO);
@@ -629,7 +629,7 @@ public abstract class AbstractITCase {
     }
 
     @SuppressWarnings("unchecked")
-    protected static <T extends PolicyTO> T createPolicy(final PolicyType type, final T policy) {
+    protected <T extends PolicyTO> T createPolicy(final PolicyType type, final T policy) {
         Response response = policyService.create(type, policy);
         if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
             Exception ex = clientFactory.getExceptionMapper().fromResponse(response);
@@ -776,7 +776,7 @@ public abstract class AbstractITCase {
         return object.get();
     }
 
-    protected static OIDCRPClientAppTO buildOIDCRP() {
+    protected OIDCRPClientAppTO buildOIDCRP() {
         AuthPolicyTO authPolicyTO = new AuthPolicyTO();
         authPolicyTO.setKey("AuthPolicyTest_" + getUUIDString());
         authPolicyTO.setName("Authentication Policy");
@@ -805,7 +805,7 @@ public abstract class AbstractITCase {
         return oidcrpTO;
     }
 
-    protected static SAML2SPClientAppTO buildSAML2SP() {
+    protected SAML2SPClientAppTO buildSAML2SP() {
         AuthPolicyTO authPolicyTO = new AuthPolicyTO();
         authPolicyTO.setKey("AuthPolicyTest_" + getUUIDString());
         authPolicyTO.setName("Authentication Policy");
@@ -846,7 +846,7 @@ public abstract class AbstractITCase {
         return (T) getObject(response.getLocation(), ClientAppService.class, clientAppTO.getClass());
     }
 
-    protected static AuthPolicyTO buildAuthPolicyTO(final String authModuleKey) {
+    protected AuthPolicyTO buildAuthPolicyTO(final String authModuleKey) {
         AuthPolicyTO policy = new AuthPolicyTO();
         policy.setName("Test Authentication policy");
 
@@ -857,7 +857,7 @@ public abstract class AbstractITCase {
         return policy;
     }
 
-    protected static AttrReleasePolicyTO buildAttrReleasePolicyTO() {
+    protected AttrReleasePolicyTO buildAttrReleasePolicyTO() {
         AttrReleasePolicyTO policy = new AttrReleasePolicyTO();
         policy.setName("Test Attribute Release policy");
 
@@ -871,7 +871,7 @@ public abstract class AbstractITCase {
         return policy;
     }
 
-    protected static AccessPolicyTO buildAccessPolicyTO() {
+    protected AccessPolicyTO buildAccessPolicyTO() {
         AccessPolicyTO policy = new AccessPolicyTO();
         policy.setName("Test Access policy");
         policy.setEnabled(true);
@@ -883,7 +883,7 @@ public abstract class AbstractITCase {
         return policy;
     }
 
-    protected static List<AuditEntry> query(final AuditQuery query, final int maxWaitSeconds) {
+    protected List<AuditEntry> query(final AuditQuery query, final int maxWaitSeconds) {
         int i = 0;
         List<AuditEntry> results = List.of();
         do {
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
index 8ff1763..ef79aa7 100644
--- 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
@@ -18,11 +18,11 @@
  */
 package org.apache.syncope.fit;
 
-import org.apache.syncope.common.rest.api.service.SyncopeService;
+import org.apache.syncope.common.lib.info.PlatformInfo;
 
 public class ElasticsearchDetector {
 
-    public static boolean isElasticSearchEnabled(final SyncopeService syncopeService) {
-        return syncopeService.platform().getPersistenceInfo().getAnySearchDAO().contains("Elasticsearch");
+    public static boolean isElasticSearchEnabled(final PlatformInfo platform) {
+        return platform.getPersistenceInfo().getAnySearchDAO().contains("Elasticsearch");
     }
 }
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
index 803c252..ff19242 100644
--- 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
@@ -18,11 +18,11 @@
  */
 package org.apache.syncope.fit;
 
-import org.apache.syncope.common.rest.api.service.SyncopeService;
+import org.apache.syncope.common.lib.info.PlatformInfo;
 
 public class FlowableDetector {
 
-    public static boolean isFlowableEnabledForUserWorkflow(final SyncopeService syncopeService) {
-        return syncopeService.platform().getWorkflowInfo().getUserWorkflowAdapter().contains("Flowable");
+    public static boolean isFlowableEnabledForUserWorkflow(final PlatformInfo platform) {
+        return platform.getWorkflowInfo().getUserWorkflowAdapter().contains("Flowable");
     }
 }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractConsoleITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractConsoleITCase.java
index 654d677..000b3eb 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractConsoleITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractConsoleITCase.java
@@ -49,10 +49,12 @@ import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
 
 public abstract class AbstractConsoleITCase extends AbstractUITCase {
 
     @ImportAutoConfiguration(classes = { SelfKeymasterClientContext.class, ZookeeperKeymasterClientContext.class })
+    @PropertySource("classpath:console.properties")
     @Configuration
     public static class SyncopeConsoleWebApplicationTestConfig {
 
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AnyTypesITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AnyTypesITCase.java
index 732f8bb..1d9acaa 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AnyTypesITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AnyTypesITCase.java
@@ -123,7 +123,7 @@ public class AnyTypesITCase extends AbstractTypesITCase {
 
     @Test
     public void delete() {
-        final String name = "anyTypeDelete";
+        String name = "anyTypeDelete";
         createAnyType(name);
         browsingToAnyTypes();
 
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 c7a3a17..0afcaa4 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
@@ -49,7 +49,7 @@ import org.junit.jupiter.api.Test;
 
 public class AuditITCase extends AbstractITCase {
 
-    private static AuditEntry queryWithFailure(final AuditQuery query, final int maxWaitSeconds) {
+    private 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());
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 9b65273..062d6c1 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
@@ -111,7 +111,7 @@ public class AuthenticationITCase extends AbstractITCase {
 
         // 3. as admin
         self = adminClient.self();
-        assertEquals(syncopeService.platform().getEntitlements().size(), self.getLeft().size());
+        assertEquals(adminClient.platform().getEntitlements().size(), self.getLeft().size());
         assertFalse(self.getLeft().keySet().contains(IdRepoEntitlement.ANONYMOUS));
         assertEquals(List.of(), self.getMiddle());
         assertEquals(ADMIN_UNAME, self.getRight().getUsername());
@@ -415,7 +415,7 @@ public class AuthenticationITCase extends AbstractITCase {
         String anyTypeKey = "FOLDER " + getUUIDString();
 
         // 1. no entitlement exists (yet) for the any type to be created
-        assertFalse(syncopeService.platform().getEntitlements().stream().
+        assertFalse(adminClient.platform().getEntitlements().stream().
                 anyMatch(entitlement -> entitlement.contains(anyTypeKey)));
 
         // 2. create plain schema, any type class and any type
@@ -436,7 +436,7 @@ public class AuthenticationITCase extends AbstractITCase {
         anyTypeService.create(anyTypeTO);
 
         // 2. now entitlement exists for the any type just created
-        assertTrue(syncopeService.platform().getEntitlements().stream().
+        assertTrue(adminClient.platform().getEntitlements().stream().
                 anyMatch(entitlement -> entitlement.contains(anyTypeKey)));
 
         // 3. attempt to create an instance of the type above: fail because no entitlement was assigned
@@ -495,7 +495,7 @@ public class AuthenticationITCase extends AbstractITCase {
         assertEquals(2, member.getMemberships().size());
         String memberKey = member.getKey();
 
-        if (ElasticsearchDetector.isElasticSearchEnabled(syncopeService)) {
+        if (ElasticsearchDetector.isElasticSearchEnabled(adminClient.platform())) {
             try {
                 Thread.sleep(2000);
             } catch (InterruptedException ex) {
@@ -580,7 +580,7 @@ public class AuthenticationITCase extends AbstractITCase {
 
     @Test
     public void issueSYNCOPE434() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform()));
 
         // 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 ddcd22e..d891085 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
@@ -44,7 +44,7 @@ public class BpmnProcessITCase extends AbstractITCase {
     @BeforeAll
     public static void findDefault() {
         assumeFalse(clientFactory.getContentType() == SyncopeClientFactoryBean.ContentType.YAML);
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform()));
 
         bpmnProcessService.list().stream().
                 filter(BpmnProcess::isUserWorkflow).findAny().
@@ -55,7 +55,7 @@ public class BpmnProcessITCase extends AbstractITCase {
     @BeforeEach
     public void check() {
         assumeFalse(clientFactory.getContentType() == SyncopeClientFactoryBean.ContentType.YAML);
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform()));
     }
 
     @Test
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/CamelRouteITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/CamelRouteITCase.java
index 7f6d138..82bb9b7 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/CamelRouteITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/CamelRouteITCase.java
@@ -41,7 +41,7 @@ public class CamelRouteITCase extends AbstractITCase {
 
     @BeforeEach
     public void check() {
-        assumeTrue(syncopeService.platform().getProvisioningInfo().getUserProvisioningManager().contains("Camel"));
+        assumeTrue(adminClient.platform().getProvisioningInfo().getUserProvisioningManager().contains("Camel"));
     }
 
     @Test
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ClientAppITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ClientAppITCase.java
index 36ab31d..fdea1a5 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ClientAppITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ClientAppITCase.java
@@ -186,7 +186,7 @@ public class ClientAppITCase extends AbstractITCase {
         }
     }
 
-    private static CASSPClientAppTO buildCASSP() {
+    private CASSPClientAppTO buildCASSP() {
         AuthPolicyTO authPolicyTO = new AuthPolicyTO();
         authPolicyTO.setKey("AuthPolicyTest_" + getUUIDString());
         authPolicyTO.setName("Authentication Policy");
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 fe31d97..4259e94 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
@@ -150,7 +150,7 @@ public class DynRealmITCase extends AbstractITCase {
             assertNotNull(group);
             final String groupKey = group.getKey();
 
-            if (ElasticsearchDetector.isElasticSearchEnabled(syncopeService)) {
+            if (ElasticsearchDetector.isElasticSearchEnabled(adminClient.platform())) {
                 try {
                     Thread.sleep(2000);
                 } catch (InterruptedException ex) {
@@ -271,7 +271,7 @@ public class DynRealmITCase extends AbstractITCase {
             assertNotNull(user.getKey());
 
             // 4a. check that Elasticsearch index was updated correctly
-            if (ElasticsearchDetector.isElasticSearchEnabled(syncopeService)) {
+            if (ElasticsearchDetector.isElasticSearchEnabled(adminClient.platform())) {
                 try {
                     Thread.sleep(2000);
                 } catch (InterruptedException ex) {
@@ -293,7 +293,7 @@ public class DynRealmITCase extends AbstractITCase {
             dynRealmService.update(dynRealm);
 
             // 6a. check that Elasticsearch index was updated correctly
-            if (ElasticsearchDetector.isElasticSearchEnabled(syncopeService)) {
+            if (ElasticsearchDetector.isElasticSearchEnabled(adminClient.platform())) {
                 try {
                     Thread.sleep(2000);
                 } catch (InterruptedException ex) {
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 0deb7bd..162f9aa 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
@@ -220,7 +220,7 @@ public class KeymasterITCase extends AbstractITCase {
 
         domainOps.create(new Domain.Builder(key).
                 jdbcDriver("org.h2.Driver").
-                jdbcURL("jdbc:h2:mem:syncopetest;DB_CLOSE_DELAY=-1").
+                jdbcURL("jdbc:h2:mem:syncopetest" + key + ";DB_CLOSE_DELAY=-1").
                 dbUsername("sa").
                 dbPassword("").
                 databasePlatform("org.apache.openjpa.jdbc.sql.H2Dictionary").
@@ -282,7 +282,7 @@ public class KeymasterITCase extends AbstractITCase {
         assertNotNull(user);
         assertEquals("monteverdi", user.getUsername());
 
-        if (ElasticsearchDetector.isElasticSearchEnabled(syncopeService)) {
+        if (ElasticsearchDetector.isElasticSearchEnabled(adminClient.platform())) {
             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 ecb61a2..acb10d0 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
@@ -279,7 +279,7 @@ public class MembershipITCase extends AbstractITCase {
             assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(execution.getStatus()));
 
             // 5. verify that pulled user has
-            if (ElasticsearchDetector.isElasticSearchEnabled(syncopeService)) {
+            if (ElasticsearchDetector.isElasticSearchEnabled(adminClient.platform())) {
                 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 27cb60f..8a368a2 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
@@ -223,7 +223,7 @@ public class MultitenancyITCase extends AbstractITCase {
             assertEquals(ExecStatus.SUCCESS, ExecStatus.valueOf(status));
 
             // verify that pulled user is found
-            if (ElasticsearchDetector.isElasticSearchEnabled(syncopeService)) {
+            if (ElasticsearchDetector.isElasticSearchEnabled(anonymusClient.platform())) {
                 try {
                     Thread.sleep(2000);
                 } catch (InterruptedException ex) {
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
index 221571c..8693080 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
@@ -333,7 +333,7 @@ public class PolicyITCase extends AbstractITCase {
 
     @Test
     public void getPullCorrelationRuleJavaClasses() {
-        Set<String> classes = syncopeService.platform().
+        Set<String> classes = adminClient.platform().
                 getJavaImplInfo(IdMImplementationType.PULL_CORRELATION_RULE).get().getClasses();
         assertEquals(1, classes.size());
         assertEquals(DummyPullCorrelationRule.class.getName(), classes.iterator().next());
@@ -341,7 +341,7 @@ public class PolicyITCase extends AbstractITCase {
 
     @Test
     public void getPushCorrelationRuleJavaClasses() {
-        Set<String> classes = syncopeService.platform().
+        Set<String> classes = adminClient.platform().
                 getJavaImplInfo(IdMImplementationType.PUSH_CORRELATION_RULE).get().getClasses();
         assertEquals(1, classes.size());
         assertEquals(DummyPushCorrelationRule.class.getName(), classes.iterator().next());
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 843fa42..5c78985 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
@@ -141,7 +141,7 @@ public class PullTaskITCase extends AbstractTaskITCase {
 
     @Test
     public void getPullActionsClasses() {
-        Set<String> actions = syncopeService.platform().
+        Set<String> actions = adminClient.platform().
                 getJavaImplInfo(IdMImplementationType.PULL_ACTIONS).get().getClasses();
         assertNotNull(actions);
         assertFalse(actions.isEmpty());
@@ -188,7 +188,7 @@ public class PullTaskITCase extends AbstractTaskITCase {
 
     @Test
     public void fromCSV() throws Exception {
-        assumeFalse(ElasticsearchDetector.isElasticSearchEnabled(syncopeService));
+        assumeFalse(ElasticsearchDetector.isElasticSearchEnabled(adminClient.platform()));
 
         removeTestUsers();
 
@@ -261,7 +261,7 @@ public class PullTaskITCase extends AbstractTaskITCase {
             UserTO userTO = userService.read(inUserTO.getKey());
             assertNotNull(userTO);
             assertEquals(userName, userTO.getUsername());
-            assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService)
+            assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform())
                     ? "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));
@@ -563,7 +563,7 @@ public class PullTaskITCase extends AbstractTaskITCase {
             // 4. pull
             execProvisioningTask(taskService, TaskType.PULL, pullTask.getKey(), MAX_WAIT_SECONDS, false);
 
-            if (ElasticsearchDetector.isElasticSearchEnabled(syncopeService)) {
+            if (ElasticsearchDetector.isElasticSearchEnabled(adminClient.platform())) {
                 try {
                     Thread.sleep(2000);
                 } catch (InterruptedException ex) {
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PushTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PushTaskITCase.java
index 5c4406d..e7d705d 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PushTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PushTaskITCase.java
@@ -68,7 +68,7 @@ public class PushTaskITCase extends AbstractTaskITCase {
 
     @Test
     public void getPushActionsClasses() {
-        Set<String> actions = syncopeService.platform().
+        Set<String> actions = adminClient.platform().
                 getJavaImplInfo(IdMImplementationType.PUSH_ACTIONS).get().getClasses();
         assertNotNull(actions);
     }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java
index 6832e64..cd49551 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ReportITCase.java
@@ -84,7 +84,7 @@ public class ReportITCase extends AbstractITCase {
 
     @Test
     public void getReportletConfs() {
-        Set<String> reportletConfs = syncopeService.platform().
+        Set<String> reportletConfs = adminClient.platform().
                 getJavaImplInfo(IdRepoImplementationType.REPORTLET).get().getClasses();
         assertNotNull(reportletConfs);
         assertFalse(reportletConfs.isEmpty());
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ResourceITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ResourceITCase.java
index 901b46f..5d21807 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ResourceITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ResourceITCase.java
@@ -94,7 +94,7 @@ public class ResourceITCase extends AbstractITCase {
 
     @Test
     public void getPropagationActionsClasses() {
-        Set<String> actions = syncopeService.platform().
+        Set<String> actions = adminClient.platform().
                 getJavaImplInfo(IdMImplementationType.PROPAGATION_ACTIONS).get().getClasses();
         assertNotNull(actions);
         assertFalse(actions.isEmpty());
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SchedTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SchedTaskITCase.java
index deb2d64..c91c08d 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SchedTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SchedTaskITCase.java
@@ -56,7 +56,7 @@ public class SchedTaskITCase extends AbstractTaskITCase {
 
     @Test
     public void getJobClasses() {
-        Set<String> jobClasses = syncopeService.platform().
+        Set<String> jobClasses = adminClient.platform().
                 getJavaImplInfo(IdRepoImplementationType.TASKJOB_DELEGATE).get().getClasses();
         assertNotNull(jobClasses);
         assertFalse(jobClasses.isEmpty());
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 77384fd..c70017b 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
@@ -171,7 +171,7 @@ public class SearchITCase extends AbstractITCase {
         GroupTO group = createGroup(groupCR).getEntity();
         assertNotNull(group);
 
-        if (ElasticsearchDetector.isElasticSearchEnabled(syncopeService)) {
+        if (ElasticsearchDetector.isElasticSearchEnabled(adminClient.platform())) {
             try {
                 Thread.sleep(2000);
             } catch (InterruptedException ex) {
@@ -229,7 +229,7 @@ public class SearchITCase extends AbstractITCase {
         role = getObject(response.getLocation(), RoleService.class, RoleTO.class);
         assertNotNull(role);
 
-        if (ElasticsearchDetector.isElasticSearchEnabled(syncopeService)) {
+        if (ElasticsearchDetector.isElasticSearchEnabled(adminClient.platform())) {
             try {
                 Thread.sleep(2000);
             } catch (InterruptedException ex) {
@@ -298,7 +298,7 @@ public class SearchITCase extends AbstractITCase {
     public void searchByDate() {
         clientFactory.create("bellini", "password").self();
 
-        if (ElasticsearchDetector.isElasticSearchEnabled(syncopeService)) {
+        if (ElasticsearchDetector.isElasticSearchEnabled(adminClient.platform())) {
             try {
                 Thread.sleep(2000);
             } catch (InterruptedException ex) {
@@ -649,7 +649,7 @@ public class SearchITCase extends AbstractITCase {
                     build();
             updateAnyObject(anyObjectUR);
 
-            if (ElasticsearchDetector.isElasticSearchEnabled(syncopeService)) {
+            if (ElasticsearchDetector.isElasticSearchEnabled(adminClient.platform())) {
                 try {
                     Thread.sleep(2000);
                 } catch (InterruptedException ex) {
@@ -688,7 +688,7 @@ public class SearchITCase extends AbstractITCase {
         req.getPlainAttrs().add(new AttrPatch.Builder(attr("ctype", "ou=sample,o=isp")).build());
         userService.update(req);
 
-        if (ElasticsearchDetector.isElasticSearchEnabled(syncopeService)) {
+        if (ElasticsearchDetector.isElasticSearchEnabled(adminClient.platform())) {
             try {
                 Thread.sleep(2000);
             } catch (InterruptedException ex) {
@@ -753,7 +753,7 @@ public class SearchITCase extends AbstractITCase {
             userService.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(syncopeService)) {
+            if (!ElasticsearchDetector.isElasticSearchEnabled(adminClient.platform())) {
                 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 d2f3cca..0b865f5 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
@@ -703,7 +703,7 @@ public class UserITCase extends AbstractITCase {
 
     @Test
     public void createActivate() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform()));
 
         UserCR userCR = getUniqueSample("createActivate@syncope.apache.org");
 
@@ -738,7 +738,7 @@ public class UserITCase extends AbstractITCase {
         UserTO userTO = createUser(userCR).getEntity();
 
         assertNotNull(userTO);
-        assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService)
+        assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform())
                 ? "active"
                 : "created", userTO.getStatus());
 
@@ -774,7 +774,7 @@ public class UserITCase extends AbstractITCase {
         userCR.getResources().add(RESOURCE_NAME_LDAP);
         UserTO userTO = createUser(userCR).getEntity();
         assertNotNull(userTO);
-        assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService)
+        assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform())
                 ? "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 f104ff0..a3a262d 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
@@ -1065,7 +1065,7 @@ public class UserIssuesITCase extends AbstractITCase {
 
     @Test
     public void issueSYNCOPE391() {
-        assumeFalse(ElasticsearchDetector.isElasticSearchEnabled(syncopeService));
+        assumeFalse(ElasticsearchDetector.isElasticSearchEnabled(adminClient.platform()));
 
         // 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 3c1f48f..6919ad3 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
@@ -56,7 +56,7 @@ public class UserRequestITCase extends AbstractITCase {
     @BeforeAll
     public static void loadBpmnProcesses() throws IOException {
         assumeFalse(clientFactory.getContentType() == SyncopeClientFactoryBean.ContentType.YAML);
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform()));
 
         WebClient.client(bpmnProcessService).type(MediaType.APPLICATION_XML_TYPE);
         bpmnProcessService.set("directorGroupRequest",
@@ -70,7 +70,7 @@ public class UserRequestITCase extends AbstractITCase {
     @BeforeEach
     public void check() {
         assumeFalse(clientFactory.getContentType() == SyncopeClientFactoryBean.ContentType.YAML);
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform()));
     }
 
     @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 040c11d..1ad768d 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
@@ -72,12 +72,12 @@ public class UserSelfITCase extends AbstractITCase {
 
     @Test
     public void selfRegistrationAllowed() {
-        assertTrue(syncopeService.platform().isSelfRegAllowed());
+        assertTrue(adminClient.platform().isSelfRegAllowed());
     }
 
     @Test
     public void create() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform()));
 
         // 1. self-registration as admin: failure
         try {
@@ -99,7 +99,7 @@ public class UserSelfITCase extends AbstractITCase {
 
     @Test
     public void createAndApprove() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform()));
 
         // 1. self-create user with membership: goes 'createApproval' with resources and membership but no propagation
         UserCR userCR = UserITCase.getUniqueSample("anonymous@syncope.apache.org");
@@ -138,7 +138,7 @@ public class UserSelfITCase extends AbstractITCase {
 
     @Test
     public void createAndUnclaim() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform()));
 
         // 1. self-create user with membership: goes 'createApproval' with resources and membership but no propagation
         UserCR userCR = UserITCase.getUniqueSample("anonymous@syncope.apache.org");
@@ -231,14 +231,14 @@ public class UserSelfITCase extends AbstractITCase {
                 readEntity(new GenericType<ProvisioningResult<UserTO>>() {
                 }).getEntity();
         assertNotNull(updated);
-        assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService)
+        assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform())
                 ? "active" : "created", updated.getStatus());
         assertTrue(updated.getUsername().endsWith("XX"));
     }
 
     @Test
     public void updateWithApproval() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform()));
 
         // 1. create user as admin
         UserTO created = createUser(UserITCase.getUniqueSample("anonymous@syncope.apache.org")).getEntity();
@@ -302,7 +302,7 @@ public class UserSelfITCase extends AbstractITCase {
                 new GenericType<ProvisioningResult<UserTO>>() {
         }).getEntity();
         assertNotNull(deleted);
-        assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService)
+        assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform())
                 ? "deleteApproval" : null, deleted.getStatus());
     }
 
@@ -343,7 +343,7 @@ public class UserSelfITCase extends AbstractITCase {
         anonClient.getService(UserSelfService.class).requestPasswordReset(user.getUsername(), "Rossi");
 
         // SYNCOPE-1293:get users with token not null before requesting password reset
-        if (ElasticsearchDetector.isElasticSearchEnabled(syncopeService)) {
+        if (ElasticsearchDetector.isElasticSearchEnabled(adminClient.platform())) {
             try {
                 Thread.sleep(2000);
             } catch (InterruptedException ex) {
@@ -462,7 +462,7 @@ public class UserSelfITCase extends AbstractITCase {
 
     @Test
     public void createWithReject() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform()));
 
         UserCR userCR = UserITCase.getUniqueSample("createWithReject@syncope.apache.org");
         userCR.getResources().add(RESOURCE_NAME_TESTDB);
@@ -538,7 +538,7 @@ public class UserSelfITCase extends AbstractITCase {
 
     @Test
     public void createWithApproval() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform()));
 
         // read forms *before* any operation
         PagedResult<UserRequestForm> forms = userRequestService.listForms(new UserRequestQuery.Builder().build());
@@ -625,7 +625,7 @@ public class UserSelfITCase extends AbstractITCase {
 
     @Test
     public void updateApproval() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform()));
 
         // read forms *before* any operation
         PagedResult<UserRequestForm> forms = userRequestService.listForms(
@@ -688,7 +688,7 @@ public class UserSelfITCase extends AbstractITCase {
 
     @Test
     public void availableTasks() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform()));
 
         UserTO user = createUser(UserITCase.getUniqueSample("availableTasks@apache.org")).getEntity();
         assertEquals("active", user.getStatus());
@@ -702,7 +702,7 @@ public class UserSelfITCase extends AbstractITCase {
 
     @Test
     public void issueSYNCOPE15() {
-        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(syncopeService));
+        assumeTrue(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform()));
 
         // read forms *before* any operation
         PagedResult<UserRequestForm> forms = userRequestService.listForms(new UserRequestQuery.Builder().build());
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AbstractEnduserITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AbstractEnduserITCase.java
index 1d5ea14..080bdc6 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AbstractEnduserITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AbstractEnduserITCase.java
@@ -53,10 +53,12 @@ import org.springframework.context.annotation.Configuration;
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
+import org.springframework.context.annotation.PropertySource;
 
 public abstract class AbstractEnduserITCase extends AbstractUITCase {
 
     @ImportAutoConfiguration(classes = { SelfKeymasterClientContext.class, ZookeeperKeymasterClientContext.class })
+    @PropertySource("classpath:enduser.properties")
     @Configuration
     public static class SyncopeEnduserWebApplicationTestConfig {
 
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 2ac7f98..ffcb670 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
@@ -113,7 +113,7 @@ public class AuthenticatedITCase extends AbstractEnduserITCase {
 
         TESTER.assertRenderedPage(SelfResult.class);
 
-        assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(SYNCOPE_SERVICE)
+        assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(adminClient.platform())
                 ? "active" : "created", userService.read(username).getStatus());
         assertEquals(newEmail, userService.read(username).getPlainAttr("email").get().getValues().get(0));
 
diff --git a/ide/netbeans/src/main/java/org/apache/syncope/ide/netbeans/service/SyncopeManagerService.java b/ide/netbeans/src/main/java/org/apache/syncope/ide/netbeans/service/SyncopeManagerService.java
index 4e8697a..659fbb6 100644
--- a/ide/netbeans/src/main/java/org/apache/syncope/ide/netbeans/service/SyncopeManagerService.java
+++ b/ide/netbeans/src/main/java/org/apache/syncope/ide/netbeans/service/SyncopeManagerService.java
@@ -21,18 +21,16 @@ package org.apache.syncope.ide.netbeans.service;
 import java.util.Set;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
-import org.apache.syncope.common.rest.api.service.SyncopeService;
 
 public class SyncopeManagerService {
 
-    private final SyncopeService service;
+    private final SyncopeClient syncopeClient;
 
     public SyncopeManagerService(final String url, final String userName, final String password) {
-        SyncopeClient syncopeClient = new SyncopeClientFactoryBean().setAddress(url).create(userName, password);
-        service = syncopeClient.getService(SyncopeService.class);
+        syncopeClient = new SyncopeClientFactoryBean().setAddress(url).create(userName, password);
     }
 
     public Set<String> getImplementationTypes() {
-        return service.platform().getImplementationTypes();
+        return syncopeClient.platform().getImplementationTypes();
     }
 }
diff --git a/pom.xml b/pom.xml
index f1bc170..8368e88 100644
--- a/pom.xml
+++ b/pom.xml
@@ -406,7 +406,7 @@ under the License.
 
     <cxf.version>3.4.4</cxf.version>
     <bouncycastle.version>1.69</bouncycastle.version>
-    <nimbus-jose-jwt.version>9.10.1</nimbus-jose-jwt.version>
+    <nimbus-jose-jwt.version>9.11</nimbus-jose-jwt.version>
 
     <jackson.version>2.12.4</jackson.version>
 
@@ -473,7 +473,7 @@ under the License.
     <chartjs.version>1.0.2</chartjs.version>
 
     <wicket.version>9.4.0</wicket.version>
-    <wicketstuff.version>9.3.0</wicketstuff.version>
+    <wicketstuff.version>9.4.0</wicketstuff.version>
     <wicket-jqueryui.version>9.3.2</wicket-jqueryui.version>
     <wicket-bootstrap.version>5.0.5</wicket-bootstrap.version>
     <wicket-spring-boot.version>3.0.4</wicket-spring-boot.version>
@@ -908,6 +908,22 @@ under the License.
 
       <dependency>
         <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-actuator</artifactId>
+        <version>${spring-boot.version}</version>
+        <exclusions>
+          <exclusion>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-logging</artifactId>
+          </exclusion>
+          <exclusion>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-tomcat</artifactId>
+          </exclusion>
+        </exclusions>
+      </dependency>
+
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-logging</artifactId>
         <exclusions>
           <exclusion>
@@ -2155,6 +2171,25 @@ under the License.
         </plugin>
 
         <plugin>
+          <groupId>io.github.git-commit-id</groupId>
+          <artifactId>git-commit-id-maven-plugin</artifactId>
+          <version>5.0.0</version>
+          <executions>
+            <execution>
+              <goals>
+                <goal>revision</goal>
+              </goals>
+            </execution>
+          </executions>
+          <configuration>
+            <verbose>true</verbose>
+            <dateFormat>yyyy-MM-dd'T'HH:mm:ssZ</dateFormat>
+            <generateGitPropertiesFile>true</generateGitPropertiesFile>
+            <generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
+          </configuration>
+        </plugin>
+
+        <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-javadoc-plugin</artifactId>
           <version>3.3.0</version>
@@ -2363,12 +2398,6 @@ under the License.
 
         <plugin>
           <groupId>org.codehaus.mojo</groupId>
-          <artifactId>buildnumber-maven-plugin</artifactId>
-          <version>1.4</version>
-        </plugin>
-
-        <plugin>
-          <groupId>org.codehaus.mojo</groupId>
           <artifactId>nbm-maven-plugin</artifactId>
           <version>4.1</version>
         </plugin>
diff --git a/sra/pom.xml b/sra/pom.xml
index 3daf441..15a34c1 100644
--- a/sra/pom.xml
+++ b/sra/pom.xml
@@ -188,6 +188,13 @@ under the License.
       </plugin>
 
       <plugin>
+        <groupId>io.github.git-commit-id</groupId>
+        <artifactId>git-commit-id-maven-plugin</artifactId>
+        <configuration>
+          <failOnNoGitDirectory>false</failOnNoGitDirectory>
+        </configuration>
+      </plugin>
+      <plugin>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-maven-plugin</artifactId>
         <configuration>
@@ -196,6 +203,7 @@ under the License.
         <executions>
           <execution>
             <goals>
+              <goal>build-info</goal>
               <goal>repackage</goal>
             </goals>
             <configuration>
@@ -204,6 +212,11 @@ under the License.
           </execution>
         </executions>
       </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+      </plugin>
     </plugins>
 
     <resources>
diff --git a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAApplication.java b/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAApplication.java
index d065458..131c2d8 100644
--- a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAApplication.java
+++ b/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAApplication.java
@@ -21,7 +21,8 @@ package org.apache.syncope.sra;
 import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
-import org.apache.syncope.sra.actuator.SRASessions;
+import org.apache.syncope.sra.actuate.SRASessions;
+import org.apache.syncope.sra.actuate.SyncopeCoreHealthIndicator;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -59,6 +60,12 @@ public class SyncopeSRAApplication {
         return new SRASessions(cacheManager);
     }
 
+    @ConditionalOnMissingBean
+    @Bean
+    public SyncopeCoreHealthIndicator syncopeCoreHealthIndicator() {
+        return new SyncopeCoreHealthIndicator();
+    }
+
     @Bean
     public KeymasterStart keymasterStart() {
         return new KeymasterStart(NetworkService.Type.SRA);
diff --git a/sra/src/main/java/org/apache/syncope/sra/actuator/SRASessions.java b/sra/src/main/java/org/apache/syncope/sra/actuate/SRASessions.java
similarity index 99%
rename from sra/src/main/java/org/apache/syncope/sra/actuator/SRASessions.java
rename to sra/src/main/java/org/apache/syncope/sra/actuate/SRASessions.java
index aedd1b5..0c07877 100644
--- a/sra/src/main/java/org/apache/syncope/sra/actuator/SRASessions.java
+++ b/sra/src/main/java/org/apache/syncope/sra/actuate/SRASessions.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.sra.actuator;
+package org.apache.syncope.sra.actuate;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
diff --git a/sra/src/main/java/org/apache/syncope/sra/actuate/SyncopeCoreHealthIndicator.java b/sra/src/main/java/org/apache/syncope/sra/actuate/SyncopeCoreHealthIndicator.java
new file mode 100644
index 0000000..8b679f8
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/actuate/SyncopeCoreHealthIndicator.java
@@ -0,0 +1,68 @@
+/*
+ * 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.sra.actuate;
+
+import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
+import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
+import org.apache.syncope.common.keymaster.client.api.ServiceOps;
+import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
+import org.apache.syncope.common.rest.api.service.SRARouteService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.boot.actuate.health.Status;
+
+public class SyncopeCoreHealthIndicator implements HealthIndicator {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(SyncopeCoreHealthIndicator.class);
+
+    @Autowired
+    protected ServiceOps serviceOps;
+
+    @Value("${anonymousUser}")
+    protected String anonymousUser;
+
+    @Value("${anonymousKey}")
+    protected String anonymousKey;
+
+    @Value("${useGZIPCompression:false}")
+    protected boolean useGZIPCompression;
+
+    @Override
+    public Health health() {
+        Health.Builder builder = new Health.Builder();
+
+        try {
+            new SyncopeClientFactoryBean().
+                    setAddress(serviceOps.get(NetworkService.Type.CORE).getAddress()).
+                    setUseCompression(useGZIPCompression).
+                    create(new AnonymousAuthenticationHandler(anonymousUser, anonymousKey)).
+                    getService(SRARouteService.class).list();
+            builder.status(Status.UP);
+        } catch (Exception e) {
+            LOG.debug("When attempting to connect to Syncope Core", e);
+            builder.status(Status.DOWN);
+        }
+
+        return builder.build();
+    }
+}
diff --git a/sra/src/main/resources/application.properties b/sra/src/main/resources/application.properties
index 4f5003b..b0b438d 100644
--- a/sra/src/main/resources/application.properties
+++ b/sra/src/main/resources/application.properties
@@ -20,8 +20,9 @@ spring.main.banner-mode=log
 
 server.port=8080
 
-management.security.enabled=true
 management.endpoint.gateway.enabled=true
-management.endpoints.web.exposure.include=health,loggers,metrics,gateway,sraSessions
+management.endpoints.web.exposure.include=info,health,loggers,metrics,gateway,sraSessions
+management.endpoint.health.show-details=ALWAYS
+spring.cloud.discovery.client.health-indicator.enabled=false
 
 service.discovery.address=http://localhost:8080/
diff --git a/wa/starter/pom.xml b/wa/starter/pom.xml
index 09defb8..9195bfd 100644
--- a/wa/starter/pom.xml
+++ b/wa/starter/pom.xml
@@ -367,6 +367,25 @@ under the License.
   <build>
     <plugins>
       <plugin>
+        <groupId>io.github.git-commit-id</groupId>
+        <artifactId>git-commit-id-maven-plugin</artifactId>
+        <configuration>
+          <failOnNoGitDirectory>false</failOnNoGitDirectory>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>build-info</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-checkstyle-plugin</artifactId>
       </plugin>
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/actuate/SyncopeCoreHealthIndicator.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/actuate/SyncopeCoreHealthIndicator.java
new file mode 100644
index 0000000..d2bf0a4
--- /dev/null
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/actuate/SyncopeCoreHealthIndicator.java
@@ -0,0 +1,53 @@
+/*
+ * 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.wa.starter.actuate;
+
+import org.apache.syncope.common.rest.api.service.UserSelfService;
+import org.apache.syncope.wa.bootstrap.WARestClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.boot.actuate.health.Status;
+
+public class SyncopeCoreHealthIndicator implements HealthIndicator {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(SyncopeCoreHealthIndicator.class);
+
+    protected final WARestClient waRestClient;
+
+    public SyncopeCoreHealthIndicator(final WARestClient waRestClient) {
+        this.waRestClient = waRestClient;
+    }
+
+    @Override
+    public Health health() {
+        Health.Builder builder = new Health.Builder();
+
+        try {
+            waRestClient.getSyncopeClient().getService(UserSelfService.class).read();
+            builder.status(Status.UP);
+        } catch (Exception e) {
+            LOG.debug("When attempting to connect to Syncope Core", e);
+            builder.status(Status.DOWN);
+        }
+
+        return builder.build();
+    }
+}
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/SyncopeWAConfiguration.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/SyncopeWAConfiguration.java
index c5173f3..4283c0d 100644
--- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/SyncopeWAConfiguration.java
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/SyncopeWAConfiguration.java
@@ -36,6 +36,7 @@ import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
 import org.apache.syncope.common.lib.types.JWSAlgorithm;
 import org.apache.syncope.wa.bootstrap.WARestClient;
+import org.apache.syncope.wa.starter.actuate.SyncopeCoreHealthIndicator;
 import org.apache.syncope.wa.starter.audit.SyncopeWAAuditTrailManager;
 import org.apache.syncope.wa.starter.events.SyncopeWAEventRepository;
 import org.apache.syncope.wa.starter.gauth.SyncopeWAGoogleMfaAuthCredentialRepository;
@@ -305,6 +306,13 @@ public class SyncopeWAConfiguration {
         return new SyncopeWASurrogateAuthenticationService(restClient);
     }
 
+    @ConditionalOnMissingBean
+    @Bean
+    @Autowired
+    public SyncopeCoreHealthIndicator syncopeCoreHealthIndicator(final WARestClient restClient) {
+        return new SyncopeCoreHealthIndicator(restClient);
+    }
+
     @Bean
     public KeymasterStart keymasterStart() {
         return new KeymasterStart(NetworkService.Type.WA);
diff --git a/wa/starter/src/main/resources/application.properties b/wa/starter/src/main/resources/application.properties
index 844b6e3..8a714d9 100644
--- a/wa/starter/src/main/resources/application.properties
+++ b/wa/starter/src/main/resources/application.properties
@@ -34,7 +34,9 @@ spring.web.resources.static-locations=classpath:/thymeleaf/static,classpath:/syn
 
 cas.monitor.endpoints.endpoint.defaults.access=AUTHENTICATED
 management.endpoints.enabled-by-default=true
-management.endpoints.web.exposure.include=*
+management.endpoints.web.exposure.include=info,health,loggers,ssoSessions
+management.endpoint.health.show-details=ALWAYS
+spring.cloud.discovery.client.health-indicator.enabled=false
 
 # Cache service definitions for 5 minutes
 cas.service-registry.cache.duration=PT5M