[SYNCOPE-1694] Refactor ImplementationManager to allow either per-class and per-instance caches (#372)

diff --git a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/ConnectorCapability.java b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/ConnectorCapability.java
index 1abf35f..70c9f41 100644
--- a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/ConnectorCapability.java
+++ b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/ConnectorCapability.java
@@ -26,6 +26,7 @@
     AUTHENTICATE,
     CREATE,
     UPDATE,
+    UPDATE_DELTA,
     DELETE,
     SEARCH,
     SYNC;
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java
index c7a82a6..1a21f40 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AbstractAnyLogic.java
@@ -20,6 +20,8 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.request.AnyCR;
@@ -52,6 +54,8 @@
 
     protected final TemplateUtils templateUtils;
 
+    protected final Map<String, LogicActions> perContextActions = new ConcurrentHashMap<>();
+
     public AbstractAnyLogic(
             final RealmDAO realmDAO,
             final AnyTypeDAO anyTypeDAO,
@@ -63,17 +67,20 @@
     }
 
     protected List<LogicActions> getActions(final Realm realm) {
-        List<LogicActions> actions = new ArrayList<>();
+        List<LogicActions> result = new ArrayList<>();
 
         realm.getActions().forEach(impl -> {
             try {
-                actions.add(ImplementationManager.build(impl));
+                result.add(ImplementationManager.build(
+                        impl,
+                        () -> perContextActions.get(impl.getKey()),
+                        instance -> perContextActions.put(impl.getKey(), instance)));
             } catch (Exception e) {
                 LOG.warn("While building {}", impl, e);
             }
         });
 
-        return actions;
+        return result;
     }
 
     @SuppressWarnings("unchecked")
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
index a673dae..a9e3194 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
@@ -36,7 +36,7 @@
 import org.apache.syncope.core.logic.audit.AuditAppender;
 import org.apache.syncope.core.logic.audit.JdbcAuditAppender;
 import org.apache.syncope.core.persistence.api.ImplementationLookup;
-import org.apache.syncope.core.persistence.api.attrvalue.validation.Validator;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValueValidator;
 import org.apache.syncope.core.persistence.api.dao.AccountRule;
 import org.apache.syncope.core.persistence.api.dao.AccountRuleConfClass;
 import org.apache.syncope.core.persistence.api.dao.PasswordRule;
@@ -225,7 +225,7 @@
                     classNames.get(IdMImplementationType.PUSH_ACTIONS).add(bd.getBeanClassName());
                 }
 
-                if (Validator.class.isAssignableFrom(clazz) && !isAbstractClazz) {
+                if (PlainAttrValueValidator.class.isAssignableFrom(clazz) && !isAbstractClazz) {
                     classNames.get(IdRepoImplementationType.VALIDATOR).add(bd.getBeanClassName());
                 }
 
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/PlainAttrValidationManager.java
similarity index 88%
copy from core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java
copy to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/PlainAttrValidationManager.java
index 5b49b4c..f686162 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/PlainAttrValidationManager.java
@@ -21,9 +21,7 @@
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 
-public interface Validator {
+public interface PlainAttrValidationManager {
 
-    void setSchema(PlainSchema schema);
-
-    void validate(String value, PlainAttrValue attrValue);
+    void validate(PlainSchema schema, String value, PlainAttrValue attrValue);
 }
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/PlainAttrValueValidator.java
similarity index 88%
rename from core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java
rename to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/PlainAttrValueValidator.java
index 5b49b4c..44d1f21 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/PlainAttrValueValidator.java
@@ -21,9 +21,7 @@
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 
-public interface Validator {
+public interface PlainAttrValueValidator {
 
-    void setSchema(PlainSchema schema);
-
-    void validate(String value, PlainAttrValue attrValue);
+    void validate(PlainSchema schema, String value, PlainAttrValue attrValue);
 }
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
index 4c65c76..0ceb427 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
@@ -24,6 +24,7 @@
 import org.apache.syncope.common.lib.request.AnyUR;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyDAO;
 
 public interface AnyUtils {
@@ -58,5 +59,5 @@
 
     Set<ExternalResource> getAllResources(Any<?> any);
 
-    void addAttr(String key, PlainSchema schema, String value);
+    void addAttr(PlainAttrValidationManager validator, String key, PlainSchema schema, String value);
 }
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/PlainAttr.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/PlainAttr.java
index 2e44dce..15376c3 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/PlainAttr.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/PlainAttr.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.persistence.api.entity;
 
 import java.util.List;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 
 public interface PlainAttr<A extends Any<?>> extends Entity {
 
@@ -30,9 +31,9 @@
 
     void setSchema(PlainSchema schema);
 
-    void add(String value, AnyUtils anyUtils);
+    void add(PlainAttrValidationManager validator, String value, AnyUtils anyUtils);
 
-    void add(String value, PlainAttrValue attrValue);
+    void add(PlainAttrValidationManager validator, String value, PlainAttrValue attrValue);
 
     PlainAttrUniqueValue getUniqueValue();
 
@@ -41,5 +42,4 @@
     List<? extends PlainAttrValue> getValues();
 
     List<String> getValuesAsStrings();
-
 }
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/MyJPAJSONPersistenceContext.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/MyJPAJSONPersistenceContext.java
index ab01aac..b638bb1 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/MyJPAJSONPersistenceContext.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/MyJPAJSONPersistenceContext.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.persistence.jpa;
 
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.AuditConfDAO;
@@ -66,7 +67,8 @@
             final @Lazy AnyObjectDAO anyObjectDAO,
             final @Lazy PlainSchemaDAO schemaDAO,
             final @Lazy EntityFactory entityFactory,
-            final AnyUtilsFactory anyUtilsFactory) {
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
 
         return new MyJPAJSONAnySearchDAO(
                 realmDAO,
@@ -76,7 +78,8 @@
                 anyObjectDAO,
                 schemaDAO,
                 entityFactory,
-                anyUtilsFactory);
+                anyUtilsFactory,
+                validator);
     }
 
     @ConditionalOnMissingBean(name = "myJPAJSONAuditConfDAO")
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/PGJPAJSONPersistenceContext.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/PGJPAJSONPersistenceContext.java
index 88e2949..551b2c8 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/PGJPAJSONPersistenceContext.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/PGJPAJSONPersistenceContext.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.persistence.jpa;
 
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.AuditConfDAO;
@@ -64,9 +65,10 @@
             final @Lazy UserDAO userDAO,
             final @Lazy GroupDAO groupDAO,
             final @Lazy AnyObjectDAO anyObjectDAO,
-            final @Lazy PlainSchemaDAO plainSchemaDAO,
+            final @Lazy PlainSchemaDAO schemaDAO,
             final @Lazy EntityFactory entityFactory,
-            final AnyUtilsFactory anyUtilsFactory) {
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
 
         return new PGJPAJSONAnySearchDAO(
                 realmDAO,
@@ -74,9 +76,10 @@
                 userDAO,
                 groupDAO,
                 anyObjectDAO,
-                plainSchemaDAO,
+                schemaDAO,
                 entityFactory,
-                anyUtilsFactory);
+                anyUtilsFactory,
+                validator);
     }
 
     @ConditionalOnMissingBean(name = "pgJPAJSONAuditConfDAO")
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnySearchDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnySearchDAO.java
index 9d29051..a76fe02 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnySearchDAO.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnySearchDAO.java
@@ -25,6 +25,7 @@
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -54,9 +55,19 @@
             final AnyObjectDAO anyObjectDAO,
             final PlainSchemaDAO schemaDAO,
             final EntityFactory entityFactory,
-            final AnyUtilsFactory anyUtilsFactory) {
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
 
-        super(realmDAO, dynRealmDAO, userDAO, groupDAO, anyObjectDAO, schemaDAO, entityFactory, anyUtilsFactory);
+        super(
+                realmDAO,
+                dynRealmDAO,
+                userDAO,
+                groupDAO,
+                anyObjectDAO,
+                schemaDAO,
+                entityFactory,
+                anyUtilsFactory,
+                validator);
     }
 
     @Override
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java
index 4c73c53..c9a209d 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java
@@ -33,6 +33,7 @@
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.common.rest.api.service.JAXRSService;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -88,11 +89,21 @@
             final UserDAO userDAO,
             final GroupDAO groupDAO,
             final AnyObjectDAO anyObjectDAO,
-            final PlainSchemaDAO plainSchemaDAO,
+            final PlainSchemaDAO schemaDAO,
             final EntityFactory entityFactory,
-            final AnyUtilsFactory anyUtilsFactory) {
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
 
-        super(realmDAO, dynRealmDAO, userDAO, groupDAO, anyObjectDAO, plainSchemaDAO, entityFactory, anyUtilsFactory);
+        super(
+                realmDAO,
+                dynRealmDAO,
+                userDAO,
+                groupDAO,
+                anyObjectDAO,
+                schemaDAO,
+                entityFactory,
+                anyUtilsFactory,
+                validator);
     }
 
     @Override
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAJSONAttributableValidator.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAJSONAttributableValidator.java
index aa72fd4..0d93175 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAJSONAttributableValidator.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAJSONAttributableValidator.java
@@ -29,7 +29,7 @@
     public boolean isValid(final JSONAttributable<?> entity, final ConstraintValidatorContext context) {
         context.disableDefaultConstraintViolation();
 
-        PlainAttrValidator attrValidator = new PlainAttrValidator();
+        JPAPlainAttrValidator attrValidator = new JPAPlainAttrValidator();
         PlainAttrValueValidator attrValueValidator = new PlainAttrValueValidator();
 
         AtomicReference<Boolean> isValid = new AtomicReference<>(Boolean.TRUE);
diff --git a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
index c3799e7..b959f52 100644
--- a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
@@ -545,7 +545,7 @@
                 connectorName="net.tirasa.connid.bundles.ldap.LdapConnector"
                 version="${connid.ldap.version}" 
                 jsonConf='[{"schema":{"name":"host","type":"java.lang.String","required":true,"order":1,"confidential":false,"defaultValues":[]},"values":["localhost"],"overridable":false},{"schema":{"name":"port","type":"int","required":false,"order":2,"confidential":false,"defaultValues":[389]},"values":[1389],"overridable":false},{"schema":{"name":"ssl","type":"boolean","required":false,"order":3,"confidential":false,"defaultValues":[false]},"values":["false"],"overridable":false},{"schema":{"name":"failover","type":"[Ljava.lang.String;","required":false,"order":4,"confidential":false,"defaultValues":[]},"values":[],"overridable":false},{"schema":{"name":"principal","type":"java.lang.String","required":false,"order":5,"confidential":false,"defaultValues":[]},"values":["uid=admin,ou=system"],"overridable":false},{"schema":{"name":"credentials","type":"org.identityconnectors.common.security.GuardedString","required":false,"order":6,"confidential":true,"defaultValues":[]},"values":["secret"],"overridable":false},{"schema":{"name":"baseContexts","type":"[Ljava.lang.String;","required":true,"order":7,"confidential":false,"defaultValues":[]},"values":["ou=people,o=isp","ou=groups,o=isp"],"overridable":true},{"schema":{"name":"passwordAttribute","type":"java.lang.String","required":false,"order":8,"confidential":false,"defaultValues":["userPassword"]},"values":["userpassword"],"overridable":false},{"schema":{"name":"accountObjectClasses","type":"[Ljava.lang.String;","required":false,"order":9,"confidential":false,"defaultValues":["top","person","organizationalPerson","inetOrgPerson"]},"values":["inetOrgPerson"],"overridable":false},{"schema":{"name":"accountUserNameAttributes","type":"[Ljava.lang.String;","required":false,"order":10,"confidential":false,"defaultValues":["uid","cn"]},"values":["uid"],"overridable":false},{"schema":{"name":"accountSearchFilter","type":"java.lang.String","required":false,"order":11,"confidential":false,"defaultValues":[]},"values":["uid=*"],"overridable":false},{"schema":{"name":"groupObjectClasses","type":"[Ljava.lang.String;","required":false,"order":12,"confidential":false,"defaultValues":["top","groupOfUniqueNames"]},"values":[],"overridable":false},{"schema":{"name":"groupNameAttributes","type":"[Ljava.lang.String;","required":false,"order":13,"confidential":false,"defaultValues":["cn"]},"values":["cn"],"overridable":false},{"schema":{"name":"groupMemberAttribute","type":"java.lang.String","required":false,"order":14,"confidential":false,"defaultValues":["uniqueMember"]},"values":[],"overridable":false},{"schema":{"name":"maintainLdapGroupMembership","type":"boolean","required":false,"order":15,"confidential":false,"defaultValues":[false]},"values":["true"],"overridable":false},{"schema":{"name":"maintainPosixGroupMembership","type":"boolean","required":false,"order":16,"confidential":false,"defaultValues":[false]},"values":["false"],"overridable":false},{"schema":{"name":"addPrincipalToNewGroups","type":"boolean","required":false,"order":17,"confidential":false,"defaultValues":[false]},"values":["true"],"overridable":false},{"schema":{"name":"passwordHashAlgorithm","type":"java.lang.String","required":false,"order":18,"confidential":false,"defaultValues":[]},"values":["SHA"],"overridable":false},{"schema":{"name":"respectResourcePasswordPolicyChangeAfterReset","type":"boolean","required":false,"order":19,"confidential":false,"defaultValues":[false]},"values":["false"],"overridable":false},{"schema":{"name":"useVlvControls","type":"boolean","required":false,"order":20,"confidential":false,"defaultValues":[false]},"values":[],"overridable":false},{"schema":{"name":"vlvSortAttribute","type":"java.lang.String","required":false,"order":21,"confidential":false,"defaultValues":["uid"]},"values":[],"overridable":false},{"schema":{"name":"uidAttribute","type":"java.lang.String","required":false,"order":22,"confidential":false,"defaultValues":["entryUUID"]},"values":["cn"],"overridable":true},{"schema":{"name":"gidAttribute","type":"java.lang.String","required":false,"order":23,"confidential":false,"defaultValues":["entryUUID"]},"values":["cn"],"overridable":true},{"schema":{"name":"readSchema","type":"boolean","required":false,"order":23,"confidential":false,"defaultValues":[true]},"values":["true"],"overridable":false},{"schema":{"name":"baseContextsToSynchronize","type":"[Ljava.lang.String;","required":false,"order":24,"confidential":false,"defaultValues":[]},"values":["ou=people,o=isp","ou=groups,o=isp"],"overridable":false},{"schema":{"name":"objectClassesToSynchronize","type":"[Ljava.lang.String;","required":false,"order":25,"confidential":false,"defaultValues":["inetOrgPerson"]},"values":["inetOrgPerson","groupOfUniqueNames"],"overridable":false},{"schema":{"name":"attributesToSynchronize","type":"[Ljava.lang.String;","required":false,"order":26,"confidential":false,"defaultValues":[]},"values":[],"overridable":false},{"schema":{"name":"modifiersNamesToFilterOut","type":"[Ljava.lang.String;","required":false,"order":27,"confidential":false,"defaultValues":[]},"values":[],"overridable":false},{"schema":{"name":"accountSynchronizationFilter","type":"java.lang.String","required":false,"order":28,"confidential":false,"defaultValues":[]},"values":[],"overridable":false},{"schema":{"name":"changeLogBlockSize","type":"int","required":false,"order":29,"confidential":false,"defaultValues":[100]},"values":[100],"overridable":false},{"schema":{"name":"changeNumberAttribute","type":"java.lang.String","required":false,"order":30,"confidential":false,"defaultValues":["changeNumber"]},"values":["changeNumber"],"overridable":false},{"schema":{"name":"filterWithOrInsteadOfAnd","type":"boolean","required":false,"order":31,"confidential":false,"defaultValues":[false]},"values":["false"],"overridable":false},{"schema":{"name":"removeLogEntryObjectClassFromFilter","type":"boolean","required":false,"order":32,"confidential":false,"defaultValues":[true]},"values":["false"],"overridable":false},{"schema":{"name":"synchronizePasswords","type":"boolean","required":false,"order":33,"confidential":false,"defaultValues":[false]},"values":["false"],"overridable":false},{"schema":{"name":"passwordAttributeToSynchronize","type":"java.lang.String","required":false,"order":34,"confidential":false,"defaultValues":[]},"values":[],"overridable":false},{"schema":{"name":"passwordDecryptionKey","type":"org.identityconnectors.common.security.GuardedByteArray","required":false,"order":35,"confidential":true,"defaultValues":[]},"values":[],"overridable":false},{"schema":{"name":"passwordDecryptionInitializationVector","type":"org.identityconnectors.common.security.GuardedByteArray","required":false,"order":36,"confidential":true,"defaultValues":[]},"values":[],"overridable":false},{"schema":{"name":"statusManagementClass","type":"java.lang.String","required":false,"order":37,"confidential":false,"defaultValues":[]},"values":["net.tirasa.connid.bundles.ldap.commons.AttributeStatusManagement"],"overridable":false},{"schema":{"name":"retrievePasswordsWithSearch","type":"boolean","required":false,"order":38,"confidential":false,"defaultValues":[false]},"values":[],"overridable":false},{"schema":{"name":"dnAttribute","type":"java.lang.String","required":false,"order":39,"confidential":false,"defaultValues":["entryDN"]},"values":[],"overridable":false},{"schema":{"name":"groupSearchFilter","type":"java.lang.String","required":false,"order":40,"confidential":false,"defaultValues":[]},"values":[],"overridable":false},{"schema":{"name":"readTimeout","type":"long","required":false,"order":41,"confidential":false,"defaultValues":[0]},"values":[],"overridable":false},{"schema":{"name":"connectTimeout","type":"long","required":false,"order":42,"confidential":false,"defaultValues":[0]},"values":[],"overridable":false}]'
-                capabilities='["CREATE","UPDATE","DELETE","SEARCH"]'/>
+                capabilities='["CREATE","UPDATE","UPDATE_DELTA","DELETE","SEARCH"]'/>
   
   <ConnInstance id="a28abd9b-9f4a-4ef6-a7a8-d19ad2a8f29d" displayName="H2-test2"
                 adminRealm_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28"
@@ -570,7 +570,7 @@
                 connectorName="net.tirasa.connid.bundles.db.scriptedsql.ScriptedSQLConnector"
                 displayName="Scripted SQL" version="${connid.database.version}"
                 jsonConf='[{&quot;schema&quot;:{&quot;name&quot;:&quot;updateScriptFileName&quot;,&quot;displayName&quot;:&quot;updateScriptFileName&quot;,&quot;helpMessage&quot;:&quot;updateScriptFileName&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;${conf.directory}/scriptedsql/UpdateScript.groovy&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;testScript&quot;,&quot;displayName&quot;:&quot;testScript&quot;,&quot;helpMessage&quot;:&quot;testScript&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;host&quot;,&quot;displayName&quot;:&quot;Host&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;Host&lt;/b&gt;&lt;br/&gt;Enter the name of the host where the database is running.&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:2,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;localhost&quot;]},&quot;overridable&quot;:false},{&quot;schema&quot;:{&quot;name&quot;:&quot;port&quot;,&quot;displayName&quot;:&quot;Port&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;TCP Port&lt;/b&gt;&lt;br/&gt;Enter the port number the database server is listening on.&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:3,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;3306&quot;]},&quot;overridable&quot;:false},{&quot;schema&quot;:{&quot;name&quot;:&quot;database&quot;,&quot;displayName&quot;:&quot;Database&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;Database&lt;/b&gt;&lt;br/&gt;Enter the name of the database on the database server that contains the table.&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:6,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false},{&quot;schema&quot;:{&quot;name&quot;:&quot;createScript&quot;,&quot;displayName&quot;:&quot;createScript&quot;,&quot;helpMessage&quot;:&quot;createScript&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;jdbcUrlTemplate&quot;,&quot;displayName&quot;:&quot;JDBC Connection URL&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;JDBC Connection URL&lt;/b&gt;&lt;br/&gt;Specify the JDBC Driver Connection URL.&lt;br/&gt; Oracle template is jdbc:oracle:thin:@[host]:[port(1521)]:[DB].&lt;br/&gt;  MySQL template is jdbc:mysql://[host]:[port(3306)]/[db], for more info, read the JDBC driver documentation.&lt;br/&gt;Could be empty if datasource is provided.&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:11,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;jdbc:mysql://%h:%p/%d&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;${testdb.url}&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;jndiProperties&quot;,&quot;displayName&quot;:&quot;Initial JNDI Properties&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;Initial JNDI Properties&lt;/b&gt;&lt;br/&gt;Could be empty or enter the JDBC JNDI Initial context factory, context provider in a format: key = value.&quot;,&quot;type&quot;:&quot;[Ljava.lang.String;&quot;,&quot;required&quot;:false,&quot;order&quot;:21,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;enableEmptyString&quot;,&quot;displayName&quot;:&quot;Enable writing empty string&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;Enable writing empty string&lt;/b&gt;&lt;br/&gt;Select to enable support for writing an empty strings, instead of a NULL value, in character based columns defined as not-null in the table schema. This option does not influence the way strings are written for Oracle based tables. By default empty strings are written as a NULL value.&quot;,&quot;type&quot;:&quot;boolean&quot;,&quot;required&quot;:false,&quot;order&quot;:12,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[false]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;false&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;allNative&quot;,&quot;displayName&quot;:&quot;All native&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;All native&lt;/b&gt;&lt;br/&gt;Select to retrieve all data type of the columns in a native format from the database table.&quot;,&quot;type&quot;:&quot;boolean&quot;,&quot;required&quot;:false,&quot;order&quot;:16,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[false]},&quot;overridable&quot;:false,&quot;values&quot;:[false]},{&quot;schema&quot;:{&quot;name&quot;:&quot;password&quot;,&quot;displayName&quot;:&quot;User Password&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;User Password&lt;/b&gt;&lt;br/&gt;Enter a user account that has permission to access accounts table.&quot;,&quot;type&quot;:&quot;org.identityconnectors.common.security.GuardedString&quot;,&quot;required&quot;:false,&quot;order&quot;:5,&quot;confidential&quot;:true,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;${testdb.password}&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;validConnectionQuery&quot;,&quot;displayName&quot;:&quot;Validate Connection Query&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;Validate Connection Query&lt;/b&gt;&lt;br/&gt;There can be specified the check connection alive query. If empty, default implementation will test it using the switch on/off the autocommit. Some select 1 from dummy table could be more efficient.&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:17,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;reloadScriptOnExecution&quot;,&quot;displayName&quot;:&quot;reloadScriptOnExecution&quot;,&quot;helpMessage&quot;:&quot;reloadScriptOnExecution&quot;,&quot;type&quot;:&quot;boolean&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[false]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;true&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;schemaScriptFileName&quot;,&quot;displayName&quot;:&quot;schemaScriptFileName&quot;,&quot;helpMessage&quot;:&quot;schemaScriptFileName&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:true,&quot;values&quot;:[&quot;${conf.directory}/scriptedsql/SchemaScript.groovy&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;jdbcDriver&quot;,&quot;displayName&quot;:&quot;JDBC Driver&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;JDBC Driver&lt;/b&gt;&lt;br/&gt;Specify the JDBC Driver class name. Oracle is oracle.jdbc.driver.OracleDriver. MySQL is org.gjt.mm.mysql.Driver.&lt;br/&gt;Could be empty if datasource is provided.&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:10,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;com.mysql.jdbc.Driver&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;${testdb.driver}&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;testScriptFileName&quot;,&quot;displayName&quot;:&quot;testScriptFileName&quot;,&quot;helpMessage&quot;:&quot;testScriptFileName&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:true,&quot;values&quot;:[&quot;${conf.directory}/scriptedsql/TestScript.groovy&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;quoting&quot;,&quot;displayName&quot;:&quot;Name Quoting&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;Name Quoting&lt;/b&gt;&lt;br/&gt;Select whether database column names for this resource should be quoted, and the quoting characters. By default, database column names are not quoted (None). For other selections (Single, Double, Back, or Brackets), column names will appear between single quotes, double quotes, back quotes, or brackets in the SQL generated to access the database.&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:-1,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;createScriptFileName&quot;,&quot;displayName&quot;:&quot;createScriptFileName&quot;,&quot;helpMessage&quot;:&quot;createScriptFileName&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;${conf.directory}/scriptedsql/CreateScript.groovy&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;clearTextPasswordToScript&quot;,&quot;displayName&quot;:&quot;clearTextPasswordToScript&quot;,&quot;helpMessage&quot;:&quot;clearTextPasswordToScript&quot;,&quot;type&quot;:&quot;boolean&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[true]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;false&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;nativeTimestamps&quot;,&quot;displayName&quot;:&quot;Native Timestamps&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;Native Timestamps&lt;/b&gt;&lt;br/&gt;Select to retrieve Timestamp data type of the columns in java.sql.Timestamp format from the database table.&quot;,&quot;type&quot;:&quot;boolean&quot;,&quot;required&quot;:false,&quot;order&quot;:15,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[false]},&quot;overridable&quot;:false,&quot;values&quot;:[false]},{&quot;schema&quot;:{&quot;name&quot;:&quot;syncScript&quot;,&quot;displayName&quot;:&quot;syncScript&quot;,&quot;helpMessage&quot;:&quot;syncScript&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;autoCommit&quot;,&quot;displayName&quot;:&quot;autoCommit&quot;,&quot;helpMessage&quot;:&quot;autoCommit&quot;,&quot;type&quot;:&quot;boolean&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[true]},&quot;overridable&quot;:false,&quot;values&quot;:[true]},{&quot;schema&quot;:{&quot;name&quot;:&quot;scriptingLanguage&quot;,&quot;displayName&quot;:&quot;scriptingLanguage&quot;,&quot;helpMessage&quot;:&quot;scriptingLanguage&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;GROOVY&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;GROOVY&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;datasource&quot;,&quot;displayName&quot;:&quot;Datasource Path&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;JDBC Data Source Name/Path&lt;/b&gt;&lt;br/&gt;Enter the JDBC Data Source Name/Path to connect to the Oracle server. If specified, connector will only try to connect using Datasource and ignore other resource parameters specified.&lt;br/&gt;the example value is: &lt;CODE&gt;jdbc/SampleDataSourceName&lt;/CODE&gt;&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:20,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;deleteScript&quot;,&quot;displayName&quot;:&quot;deleteScript&quot;,&quot;helpMessage&quot;:&quot;deleteScript&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;rethrowAllSQLExceptions&quot;,&quot;displayName&quot;:&quot;Rethrow all SQLExceptions&quot;,&quot;helpMessage&quot;:&quot;If this is not checked, SQL statements which throw SQLExceptions with a 0 ErrorCode will be have the exception caught and suppressed. Check it to have exceptions with 0 ErrorCodes rethrown.&quot;,&quot;type&quot;:&quot;boolean&quot;,&quot;required&quot;:false,&quot;order&quot;:14,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[true]},&quot;overridable&quot;:false,&quot;values&quot;:[true]},{&quot;schema&quot;:{&quot;name&quot;:&quot;syncScriptFileName&quot;,&quot;displayName&quot;:&quot;syncScriptFileName&quot;,&quot;helpMessage&quot;:&quot;syncScriptFileName&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:true,&quot;values&quot;:[&quot;${conf.directory}/scriptedsql/SyncScript.groovy&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;updateScript&quot;,&quot;displayName&quot;:&quot;updateScript&quot;,&quot;helpMessage&quot;:&quot;updateScript&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;user&quot;,&quot;displayName&quot;:&quot;User&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;User&lt;/b&gt;&lt;br/&gt;Enter the name of the mandatory Database user with permission to account table.&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:4,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;${testdb.username}&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;deleteScriptFileName&quot;,&quot;displayName&quot;:&quot;deleteScriptFileName&quot;,&quot;helpMessage&quot;:&quot;deleteScriptFileName&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;${conf.directory}/scriptedsql/DeleteScript.groovy&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;searchScriptFileName&quot;,&quot;displayName&quot;:&quot;searchScriptFileName&quot;,&quot;helpMessage&quot;:&quot;searchScriptFileName&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:true,&quot;values&quot;:[&quot;${conf.directory}/scriptedsql/SearchScript.groovy&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;searchScript&quot;,&quot;displayName&quot;:&quot;searchScript&quot;,&quot;helpMessage&quot;:&quot;searchScript&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[]}]'
-                capabilities='["CREATE","UPDATE","DELETE","SEARCH","SYNC"]'/>
+                capabilities='["CREATE","UPDATE","UPDATE_DELTA","DELETE","SEARCH","SYNC"]'/>
   
   <ConnInstance id="44c02549-19c3-483c-8025-4919c3283c37" bundlename="net.tirasa.connid.bundles.rest"
                 adminRealm_id="0679e069-7355-4b20-bd11-a5a0a5453c7c"
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
index b2e7b2c..cf95524 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
@@ -26,6 +26,7 @@
 import org.apache.syncope.common.keymaster.client.api.DomainOps;
 import org.apache.syncope.core.persistence.api.DomainHolder;
 import org.apache.syncope.core.persistence.api.DomainRegistry;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyMatchDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
@@ -79,6 +80,7 @@
 import org.apache.syncope.core.persistence.api.entity.policy.PolicyUtilsFactory;
 import org.apache.syncope.core.persistence.api.entity.task.TaskUtilsFactory;
 import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
+import org.apache.syncope.core.persistence.jpa.attrvalue.validation.DefaultPlainAttrValidationManager;
 import org.apache.syncope.core.persistence.jpa.content.KeymasterConfParamLoader;
 import org.apache.syncope.core.persistence.jpa.content.XMLContentExporter;
 import org.apache.syncope.core.persistence.jpa.content.XMLContentLoader;
@@ -176,6 +178,12 @@
 
     @ConditionalOnMissingBean
     @Bean
+    public PlainAttrValidationManager plainAttrValidationManager() {
+        return new DefaultPlainAttrValidationManager();
+    }
+
+    @ConditionalOnMissingBean
+    @Bean
     public CommonEntityManagerFactoryConf commonEMFConf(final PersistenceProperties persistenceProperties) {
         CommonEntityManagerFactoryConf commonEMFConf = new CommonEntityManagerFactoryConf();
         commonEMFConf.setPackagesToScan("org.apache.syncope.core.persistence.jpa.entity");
@@ -321,9 +329,17 @@
             final @Lazy AnyObjectDAO anyObjectDAO,
             final RealmDAO realmDAO,
             final PlainSchemaDAO plainSchemaDAO,
-            final AnyUtilsFactory anyUtilsFactory) {
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
 
-        return new JPAAnyMatchDAO(userDAO, groupDAO, anyObjectDAO, realmDAO, plainSchemaDAO, anyUtilsFactory);
+        return new JPAAnyMatchDAO(
+                userDAO,
+                groupDAO,
+                anyObjectDAO,
+                realmDAO,
+                plainSchemaDAO,
+                anyUtilsFactory,
+                validator);
     }
 
     @ConditionalOnMissingBean
@@ -355,7 +371,8 @@
             final @Lazy AnyObjectDAO anyObjectDAO,
             final PlainSchemaDAO schemaDAO,
             final EntityFactory entityFactory,
-            final AnyUtilsFactory anyUtilsFactory) {
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
 
         return new JPAAnySearchDAO(
                 realmDAO,
@@ -365,7 +382,8 @@
                 anyObjectDAO,
                 schemaDAO,
                 entityFactory,
-                anyUtilsFactory);
+                anyUtilsFactory,
+                validator);
     }
 
     @ConditionalOnMissingBean
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/AbstractValidator.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/AbstractValidator.java
index 8c149db..4bcecbb 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/AbstractValidator.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/AbstractValidator.java
@@ -19,30 +19,23 @@
 package org.apache.syncope.core.persistence.jpa.attrvalue.validation;
 
 import java.io.Serializable;
-import org.apache.syncope.core.persistence.api.attrvalue.validation.Validator;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValueValidator;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public abstract class AbstractValidator implements Validator, Serializable {
+public abstract class AbstractValidator implements PlainAttrValueValidator, Serializable {
 
     private static final long serialVersionUID = -5439345166669502493L;
 
     protected static final Logger LOG = LoggerFactory.getLogger(AbstractValidator.class);
 
-    protected PlainSchema schema;
-
     @Override
-    public void setSchema(final PlainSchema schema) {
-        this.schema = schema;
-    }
-
-    @Override
-    public void validate(final String value, final PlainAttrValue attrValue) {
+    public void validate(final PlainSchema schema, final String value, final PlainAttrValue attrValue) {
         attrValue.parseValue(schema, value);
-        doValidate(attrValue);
+        doValidate(schema, attrValue);
     }
 
-    protected abstract void doValidate(PlainAttrValue attrValue);
+    protected abstract void doValidate(PlainSchema schema, PlainAttrValue attrValue);
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/AlwaysTrueValidator.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/AlwaysTrueValidator.java
index 65d63ed..887eefc 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/AlwaysTrueValidator.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/AlwaysTrueValidator.java
@@ -20,13 +20,14 @@
 
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 
 public class AlwaysTrueValidator extends AbstractValidator {
 
     private static final long serialVersionUID = 872107345555773183L;
 
     @Override
-    protected void doValidate(final PlainAttrValue attrValue) {
+    protected void doValidate(final PlainSchema schema, final PlainAttrValue attrValue) {
         Boolean value = attrValue.getValue();
         if (!value) {
             throw new InvalidPlainAttrValueException("This attribute must be set to \"true\"");
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/BasicValidator.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/BasicValidator.java
index 2e62e16..13840da 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/BasicValidator.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/BasicValidator.java
@@ -22,13 +22,14 @@
 import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 
 public class BasicValidator extends AbstractValidator {
 
     private static final long serialVersionUID = -2606728447694223607L;
 
     @Override
-    protected void doValidate(final PlainAttrValue attrValue) {
+    protected void doValidate(final PlainSchema schema, final PlainAttrValue attrValue) {
         if (AttrSchemaType.Enum == schema.getType()) {
             final String[] enumeration = schema.getEnumerationValues().split(SyncopeConstants.ENUM_VALUES_SEPARATOR);
             final String value = attrValue.getStringValue();
@@ -42,7 +43,7 @@
 
             if (!found) {
                 throw new InvalidPlainAttrValueException(
-                    '\'' + value + "' is not one of: " + schema.getEnumerationValues());
+                        '\'' + value + "' is not one of: " + schema.getEnumerationValues());
             }
         }
     }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/BinaryValidator.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/BinaryValidator.java
index 0df8bb2..bc7d2fd 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/BinaryValidator.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/BinaryValidator.java
@@ -23,6 +23,7 @@
 import javax.ws.rs.core.MediaType;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.tika.Tika;
 
 public class BinaryValidator extends AbstractValidator {
@@ -38,7 +39,7 @@
     }
 
     @Override
-    protected void doValidate(final PlainAttrValue attrValue) {
+    protected void doValidate(final PlainSchema schema, final PlainAttrValue attrValue) {
         // check Binary schemas MIME Type mismatches
         if (attrValue.getBinaryValue() != null) {
             byte[] binaryValue = attrValue.getBinaryValue();
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/DefaultPlainAttrValidationManager.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/DefaultPlainAttrValidationManager.java
new file mode 100644
index 0000000..7cc0274
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/DefaultPlainAttrValidationManager.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.attrvalue.validation;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValueValidator;
+import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+import org.apache.syncope.core.spring.ImplementationManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultPlainAttrValidationManager implements PlainAttrValidationManager {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(DefaultPlainAttrValidationManager.class);
+
+    protected static final PlainAttrValueValidator BASIC_VALIDATOR = new BasicValidator();
+
+    protected final Map<String, PlainAttrValueValidator> perContextValidators = new ConcurrentHashMap<>();
+
+    @Override
+    public void validate(final PlainSchema schema, final String value, final PlainAttrValue attrValue) {
+        PlainAttrValueValidator validator = null;
+
+        if (schema.getValidator() != null) {
+            try {
+                validator = ImplementationManager.build(
+                        schema.getValidator(),
+                        () -> perContextValidators.get(schema.getValidator().getKey()),
+                        instance -> perContextValidators.put(schema.getValidator().getKey(), instance));
+            } catch (Exception e) {
+                LOG.error("While building {}", schema.getValidator(), e);
+            }
+        }
+
+        if (validator == null) {
+            validator = BASIC_VALIDATOR;
+        }
+
+        validator.validate(schema, value, attrValue);
+    }
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/EmailAddressValidator.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/EmailAddressValidator.java
index ebc9e9d..3fa8439 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/EmailAddressValidator.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/EmailAddressValidator.java
@@ -22,13 +22,14 @@
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
 import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 
 public class EmailAddressValidator extends AbstractValidator {
 
     private static final long serialVersionUID = 792457177290331518L;
 
     @Override
-    protected void doValidate(final PlainAttrValue attrValue) {
+    protected void doValidate(final PlainSchema schema, final PlainAttrValue attrValue) {
         Matcher matcher = Entity.EMAIL_PATTERN.matcher(attrValue.<CharSequence>getValue());
         if (!matcher.matches()) {
             throw new InvalidPlainAttrValueException("\"" + attrValue.getValue() + "\" is not a valid email address");
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/URLValidator.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/URLValidator.java
index 5646b0b..acb8f6b 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/URLValidator.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/attrvalue/validation/URLValidator.java
@@ -22,13 +22,14 @@
 import java.net.URL;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 
 public class URLValidator extends AbstractValidator {
 
     private static final long serialVersionUID = 792457177290331518L;
 
     @Override
-    protected void doValidate(final PlainAttrValue attrValue) {
+    protected void doValidate(final PlainSchema schema, final PlainAttrValue attrValue) {
         try {
             new URL(attrValue.getStringValue());
         } catch (MalformedURLException e) {
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java
index 53863ab..0443b45 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java
@@ -35,6 +35,7 @@
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
@@ -60,7 +61,6 @@
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
-import org.apache.syncope.core.persistence.jpa.entity.JPAPlainSchema;
 import org.springframework.util.CollectionUtils;
 
 public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implements AnySearchDAO {
@@ -126,6 +126,8 @@
 
     protected final AnyUtilsFactory anyUtilsFactory;
 
+    protected final PlainAttrValidationManager validator;
+
     public AbstractAnySearchDAO(
             final RealmDAO realmDAO,
             final DynRealmDAO dynRealmDAO,
@@ -134,7 +136,8 @@
             final AnyObjectDAO anyObjectDAO,
             final PlainSchemaDAO plainSchemaDAO,
             final EntityFactory entityFactory,
-            final AnyUtilsFactory anyUtilsFactory) {
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
 
         this.realmDAO = realmDAO;
         this.dynRealmDAO = dynRealmDAO;
@@ -144,6 +147,7 @@
         this.plainSchemaDAO = plainSchemaDAO;
         this.entityFactory = entityFactory;
         this.anyUtilsFactory = anyUtilsFactory;
+        this.validator = validator;
     }
 
     protected abstract int doCount(
@@ -210,7 +214,7 @@
                     && cond.getType() != AttrCond.Type.ISNULL
                     && cond.getType() != AttrCond.Type.ISNOTNULL) {
 
-                ((JPAPlainSchema) schema).validator().validate(cond.getExpression(), attrValue);
+                validator.validate(schema, cond.getExpression(), attrValue);
             }
         } catch (ValidationException e) {
             throw new IllegalArgumentException("Could not validate expression " + cond.getExpression());
@@ -272,7 +276,7 @@
                 && computed.getType() != AttrCond.Type.ISNOTNULL) {
 
             try {
-                ((JPAPlainSchema) schema).validator().validate(computed.getExpression(), attrValue);
+                validator.validate(schema, computed.getExpression(), attrValue);
             } catch (ValidationException e) {
                 throw new IllegalArgumentException("Could not validate expression " + computed.getExpression());
             }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyMatchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyMatchDAO.java
index add9784..d086fc8 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyMatchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyMatchDAO.java
@@ -35,6 +35,7 @@
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyMatchDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -81,13 +82,16 @@
 
     protected final AnyUtilsFactory anyUtilsFactory;
 
+    protected final PlainAttrValidationManager validator;
+
     public JPAAnyMatchDAO(
             final UserDAO userDAO,
             final GroupDAO groupDAO,
             final AnyObjectDAO anyObjectDAO,
             final RealmDAO realmDAO,
             final PlainSchemaDAO plainSchemaDAO,
-            final AnyUtilsFactory anyUtilsFactory) {
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
 
         this.userDAO = userDAO;
         this.groupDAO = groupDAO;
@@ -95,6 +99,7 @@
         this.realmDAO = realmDAO;
         this.plainSchemaDAO = plainSchemaDAO;
         this.anyUtilsFactory = anyUtilsFactory;
+        this.validator = validator;
     }
 
     /**
@@ -171,8 +176,9 @@
                 }
 
                 if (match == null) {
-                    Optional<AnyCond> anyCond = cond.getLeaf(AnyCond.class);
-                    match = anyCond.map(value -> matches(any, value, not)).orElseGet(() -> cond.getLeaf(AttrCond.class).
+                    match = cond.getLeaf(AnyCond.class).
+                            map(value -> matches(any, value, not)).
+                            orElseGet(() -> cond.getLeaf(AttrCond.class).
                             map(leaf -> matches(any, leaf, not)).
                             orElse(null));
                 }
@@ -197,12 +203,12 @@
         return false;
     }
 
-    protected static boolean matches(final Any<?> any, final AnyTypeCond cond, final boolean not) {
+    protected boolean matches(final Any<?> any, final AnyTypeCond cond, final boolean not) {
         boolean equals = any.getType().getKey().equals(cond.getAnyTypeKey());
         return not ? !equals : equals;
     }
 
-    protected static boolean matches(
+    protected boolean matches(
             final GroupableRelatable<?, ?, ?, ?, ?> any, final RelationshipTypeCond cond, final boolean not) {
 
         boolean found = any.getRelationships().stream().
@@ -284,7 +290,7 @@
     }
 
     @SuppressWarnings({ "unchecked", "rawtypes" })
-    protected static boolean matches(
+    protected boolean matches(
             final List<? extends PlainAttrValue> anyAttrValues,
             final PlainAttrValue attrValue,
             final PlainSchema schema,
@@ -374,7 +380,7 @@
                             && cond.getType() != AttrCond.Type.ISNULL
                             && cond.getType() != AttrCond.Type.ISNOTNULL) {
 
-                        ((JPAPlainSchema) schema).validator().validate(cond.getExpression(), attrValue);
+                        validator.validate(schema, cond.getExpression(), attrValue);
                     }
                 } catch (ValidationException e) {
                     LOG.error("Could not validate expression '" + cond.getExpression() + '\'', e);
@@ -466,7 +472,7 @@
                         && cond.getType() != AttrCond.Type.ISNOTNULL) {
 
                     try {
-                        ((JPAPlainSchema) schema).validator().validate(cond.getExpression(), attrValue);
+                        validator.validate(schema, cond.getExpression(), attrValue);
                     } catch (ValidationException e) {
                         LOG.error("Could not validate expression '" + cond.getExpression() + '\'', e);
                         return false;
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
index c87e434..a1bca5e 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
@@ -35,6 +35,7 @@
 import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.rest.api.service.JAXRSService;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -79,9 +80,19 @@
             final AnyObjectDAO anyObjectDAO,
             final PlainSchemaDAO plainSchemaDAO,
             final EntityFactory entityFactory,
-            final AnyUtilsFactory anyUtilsFactory) {
+            final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator) {
 
-        super(realmDAO, dynRealmDAO, userDAO, groupDAO, anyObjectDAO, plainSchemaDAO, entityFactory, anyUtilsFactory);
+        super(
+                realmDAO,
+                dynRealmDAO,
+                userDAO,
+                groupDAO,
+                anyObjectDAO,
+                plainSchemaDAO,
+                entityFactory,
+                anyUtilsFactory,
+                validator);
     }
 
     protected String buildAdminRealmsFilter(
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
index aca6eee..d31abd1 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAUserDAO.java
@@ -27,6 +27,7 @@
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 import javax.persistence.NoResultException;
 import javax.persistence.PersistenceException;
@@ -38,11 +39,13 @@
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
+import org.apache.syncope.core.persistence.api.dao.AccountRule;
 import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
 import org.apache.syncope.core.persistence.api.dao.DerSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
 import org.apache.syncope.core.persistence.api.dao.FIQLQueryDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.PasswordRule;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.dao.RoleDAO;
@@ -93,6 +96,10 @@
 
     protected final SecurityProperties securityProperties;
 
+    protected final Map<String, AccountRule> perContextAccountRules = new ConcurrentHashMap<>();
+
+    protected final Map<String, PasswordRule> perContextPasswordRules = new ConcurrentHashMap<>();
+
     public JPAUserDAO(
             final AnyUtilsFactory anyUtilsFactory,
             final PlainSchemaDAO plainSchemaDAO,
@@ -321,6 +328,42 @@
         return policies;
     }
 
+    protected List<AccountRule> getAccountRules(final AccountPolicy policy) {
+        List<AccountRule> result = new ArrayList<>();
+
+        for (Implementation impl : policy.getRules()) {
+            try {
+                ImplementationManager.buildAccountRule(
+                        impl,
+                        () -> perContextAccountRules.get(impl.getKey()),
+                        instance -> perContextAccountRules.put(impl.getKey(), instance)).
+                        ifPresent(result::add);
+            } catch (Exception e) {
+                LOG.warn("While building {}", impl, e);
+            }
+        }
+
+        return result;
+    }
+
+    protected List<PasswordRule> getPasswordRules(final PasswordPolicy policy) {
+        List<PasswordRule> result = new ArrayList<>();
+
+        for (Implementation impl : policy.getRules()) {
+            try {
+                ImplementationManager.buildPasswordRule(
+                        impl,
+                        () -> perContextPasswordRules.get(impl.getKey()),
+                        instance -> perContextPasswordRules.put(impl.getKey(), instance)).
+                        ifPresent(result::add);
+            } catch (Exception e) {
+                LOG.warn("While building {}", impl, e);
+            }
+        }
+
+        return result;
+    }
+
     @Transactional(readOnly = true)
     @Override
     public Pair<Boolean, Boolean> enforcePolicies(final User user) {
@@ -336,15 +379,13 @@
                     throw new PasswordPolicyException("Password mandatory");
                 }
 
-                for (Implementation impl : policy.getRules()) {
-                    ImplementationManager.buildPasswordRule(impl).ifPresent(rule -> {
-                        rule.enforce(user);
+                getPasswordRules(policy).forEach(rule -> {
+                    rule.enforce(user);
 
-                        user.getLinkedAccounts().stream().
-                                filter(account -> account.getPassword() != null).
-                                forEach(rule::enforce);
-                    });
-                }
+                    user.getLinkedAccounts().stream().
+                            filter(account -> account.getPassword() != null).
+                            forEach(rule::enforce);
+                });
 
                 boolean matching = false;
                 if (policy.getHistoryLength() > 0) {
@@ -419,15 +460,13 @@
                         });
             } else {
                 for (AccountPolicy policy : accountPolicies) {
-                    for (Implementation impl : policy.getRules()) {
-                        ImplementationManager.buildAccountRule(impl).ifPresent(rule -> {
-                            rule.enforce(user);
+                    getAccountRules(policy).forEach(rule -> {
+                        rule.enforce(user);
 
-                            user.getLinkedAccounts().stream().
-                                    filter(account -> account.getUsername() != null).
-                                    forEach(rule::enforce);
-                        });
-                    }
+                        user.getLinkedAccounts().stream().
+                                filter(account -> account.getUsername() != null).
+                                forEach(rule::enforce);
+                    });
 
                     suspend |= user.getFailedLogins() != null && policy.getMaxAuthenticationAttempts() > 0
                             && user.getFailedLogins() > policy.getMaxAuthenticationAttempts() && !user.isSuspended();
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractPlainAttr.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractPlainAttr.java
index b3dfd3d..0dc8ea0 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractPlainAttr.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractPlainAttr.java
@@ -26,6 +26,7 @@
 import javax.persistence.ManyToOne;
 import javax.persistence.MappedSuperclass;
 import javax.validation.constraints.NotNull;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
@@ -65,11 +66,11 @@
     }
 
     @Override
-    public void add(final String value, final PlainAttrValue attrValue) {
+    public void add(final PlainAttrValidationManager validator, final String value, final PlainAttrValue attrValue) {
         checkNonNullSchema();
 
         attrValue.setAttr(this);
-        getSchema().validator().validate(value, attrValue);
+        validator.validate(getSchema(), value, attrValue);
 
         if (getSchema().isUniqueConstraint()) {
             setUniqueValue((PlainAttrUniqueValue) attrValue);
@@ -82,7 +83,7 @@
     }
 
     @Override
-    public void add(final String value, final AnyUtils anyUtils) {
+    public void add(final PlainAttrValidationManager validator, final String value, final AnyUtils anyUtils) {
         checkNonNullSchema();
 
         PlainAttrValue attrValue;
@@ -93,7 +94,7 @@
             attrValue = anyUtils.newPlainAttrValue();
         }
 
-        add(value, attrValue);
+        add(validator, value, attrValue);
     }
 
     @Override
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
index ae37005..7867095 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
@@ -41,6 +41,7 @@
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -420,7 +421,12 @@
 
     @Transactional
     @Override
-    public void addAttr(final String key, final PlainSchema schema, final String value) {
+    public void addAttr(
+            final PlainAttrValidationManager validator,
+            final String key,
+            final PlainSchema schema,
+            final String value) {
+
         Any any = dao().find(key);
 
         Set<AnyTypeClass> typeOwnClasses = new HashSet<>();
@@ -439,7 +445,7 @@
             any.add(attr);
 
             try {
-                attr.add(value, this);
+                attr.add(validator, value, this);
                 dao().save(any);
             } catch (InvalidPlainAttrValueException e) {
                 LOG.error("Invalid value for attribute {} and {}: {}", schema.getKey(), any, value, e);
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAPlainSchema.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAPlainSchema.java
index e795177..3299bd6 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAPlainSchema.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAPlainSchema.java
@@ -27,18 +27,14 @@
 import javax.persistence.OneToOne;
 import javax.persistence.PrimaryKeyJoinColumn;
 import javax.persistence.Table;
-import javax.persistence.Transient;
 import javax.validation.constraints.NotNull;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.common.lib.types.CipherAlgorithm;
 import org.apache.syncope.common.lib.types.IdRepoImplementationType;
-import org.apache.syncope.core.persistence.api.attrvalue.validation.Validator;
 import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
-import org.apache.syncope.core.persistence.jpa.attrvalue.validation.BasicValidator;
 import org.apache.syncope.core.persistence.jpa.validation.entity.PlainSchemaCheck;
-import org.apache.syncope.core.spring.ImplementationManager;
 
 @Entity
 @Table(name = JPAPlainSchema.TABLE)
@@ -90,9 +86,6 @@
     @OneToOne
     private JPAImplementation validator;
 
-    @Transient
-    private Validator validatorImpl;
-
     @Override
     public AnyTypeClass getAnyTypeClass() {
         return anyTypeClass;
@@ -154,27 +147,6 @@
         this.readonly = readonly;
     }
 
-    public Validator validator() {
-        if (validatorImpl != null) {
-            return validatorImpl;
-        }
-
-        if (getValidator() != null) {
-            try {
-                validatorImpl = ImplementationManager.build(getValidator());
-            } catch (Exception e) {
-                LOG.error("While building {}", getValidator(), e);
-            }
-        }
-
-        if (validatorImpl == null) {
-            validatorImpl = new BasicValidator();
-        }
-        validatorImpl.setSchema(this);
-
-        return validatorImpl;
-    }
-
     @Override
     public Implementation getValidator() {
         return validator;
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PlainAttrValidator.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAPlainAttrValidator.java
similarity index 96%
rename from core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PlainAttrValidator.java
rename to core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAPlainAttrValidator.java
index 89ee1f8..1a66833 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PlainAttrValidator.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/JPAPlainAttrValidator.java
@@ -22,7 +22,7 @@
 import org.apache.syncope.common.lib.types.EntityViolationType;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
 
-public class PlainAttrValidator extends AbstractValidator<PlainAttrCheck, PlainAttr<?>> {
+public class JPAPlainAttrValidator extends AbstractValidator<PlainAttrCheck, PlainAttr<?>> {
 
     @Override
     public boolean isValid(final PlainAttr<?> attr, final ConstraintValidatorContext context) {
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PlainAttrCheck.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PlainAttrCheck.java
index 4ae6eb5..5fe4554 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PlainAttrCheck.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/validation/entity/PlainAttrCheck.java
@@ -28,7 +28,7 @@
 
 @Target({ ElementType.TYPE })
 @Retention(RetentionPolicy.RUNTIME)
-@Constraint(validatedBy = PlainAttrValidator.class)
+@Constraint(validatedBy = JPAPlainAttrValidator.class)
 @Documented
 public @interface PlainAttrCheck {
 
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainAttrTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainAttrTest.java
index b2c4cef..8c68cdc 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainAttrTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PlainAttrTest.java
@@ -36,6 +36,7 @@
 import org.apache.syncope.common.lib.types.CipherAlgorithm;
 import org.apache.syncope.common.lib.types.EntityViolationType;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainAttrDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
@@ -67,6 +68,9 @@
     @Autowired
     private AnyTypeClassDAO anyTypeClassDAO;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     @Tag("plainAttrTable")
     @Test
     public void findByKey() {
@@ -98,15 +102,15 @@
 
         Exception thrown = null;
         try {
-            attr.add("john.doe@gmail.com", anyUtilsFactory.getInstance(AnyTypeKind.USER));
-            attr.add("mario.rossi@gmail.com", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+            attr.add(validator, "john.doe@gmail.com", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+            attr.add(validator, "mario.rossi@gmail.com", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         } catch (ValidationException e) {
             thrown = e;
         }
         assertNull(thrown);
 
         try {
-            attr.add("http://www.apache.org", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+            attr.add(validator, "http://www.apache.org", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         } catch (ValidationException e) {
             thrown = e;
         }
@@ -130,13 +134,13 @@
 
         Exception thrown = null;
         try {
-            attribute.add("A", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+            attribute.add(validator, "A", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         } catch (ValidationException e) {
             thrown = e;
         }
         assertNotNull(thrown);
 
-        attribute.add("M", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attribute.add(validator, "M", anyUtilsFactory.getInstance(AnyTypeKind.USER));
 
         InvalidEntityException iee = null;
         try {
@@ -225,7 +229,7 @@
         UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
         attr.setOwner(user);
         attr.setSchema(obscureSchema);
-        attr.add("testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, "testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(attr);
 
         userDAO.save(user);
@@ -255,7 +259,7 @@
 
         UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
         attr.setSchema(obscureWithKeyAsSysprop);
-        attr.add("testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, "testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
 
         assertEquals(Encryptor.getInstance(obscureSchema.getSecretKey()).
                 encode("testvalue", obscureSchema.getCipherAlgorithm()), attr.getValues().get(0).getStringValue());
@@ -274,8 +278,8 @@
 
         UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
         attr.setSchema(obscureWithDecodeConversionPattern);
-        attr.add("testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
-        
+        attr.add(validator, "testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+
         assertEquals(Encryptor.getInstance(obscureWithDecodeConversionPattern.getSecretKey()).
                 encode("testvalue", obscureWithDecodeConversionPattern.getCipherAlgorithm()),
                 attr.getValues().get(0).getStringValue());
@@ -302,7 +306,7 @@
         UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
         attr.setOwner(user);
         attr.setSchema(photoSchema);
-        attr.add(photoB64Value, anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, photoB64Value, anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(attr);
 
         userDAO.save(user);
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AnySearchTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AnySearchTest.java
index a3ee8a9..67fd767 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AnySearchTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AnySearchTest.java
@@ -32,6 +32,7 @@
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
@@ -76,6 +77,9 @@
     @Autowired
     private PlainSchemaDAO plainSchemaDAO;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     @Test
     public void searchByDynMembership() {
         // 1. create role with dynamic membership
@@ -204,14 +208,14 @@
         GPlainAttr title = entityFactory.newEntity(GPlainAttr.class);
         title.setOwner(group);
         title.setSchema(plainSchemaDAO.find("title"));
-        title.add("syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
+        title.add(validator, "syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
         group.add(title);
 
         // unique
         GPlainAttr originalName = entityFactory.newEntity(GPlainAttr.class);
         originalName.setOwner(group);
         originalName.setSchema(plainSchemaDAO.find("originalName"));
-        originalName.add("syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
+        originalName.add(validator, "syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
         group.add(originalName);
 
         groupDAO.save(group);
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java
index c9c1c7d..c45a832 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java
@@ -35,6 +35,7 @@
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
@@ -84,6 +85,9 @@
     @Autowired
     private AnyTypeClassDAO anyTypeClassDAO;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     @Test
     public void saveWithTwoOwners() {
         assertThrows(InvalidEntityException.class, () -> {
@@ -205,7 +209,7 @@
         UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
         attr.setOwner(user);
         attr.setSchema(plainSchemaDAO.find("cool"));
-        attr.add("true", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, "true", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(attr);
 
         user = userDAO.save(user);
@@ -304,7 +308,7 @@
         APlainAttr attr = entityFactory.newEntity(APlainAttr.class);
         attr.setOwner(anyObject);
         attr.setSchema(plainSchemaDAO.find("model"));
-        attr.add("Canon MFC8030", anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT));
+        attr.add(validator, "Canon MFC8030", anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT));
         anyObject.add(attr);
 
         anyObject = anyObjectDAO.save(anyObject);
@@ -382,14 +386,14 @@
         GPlainAttr title = entityFactory.newEntity(GPlainAttr.class);
         title.setOwner(group);
         title.setSchema(plainSchemaDAO.find("title"));
-        title.add("syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
+        title.add(validator, "syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
         group.add(title);
 
         // unique
         GPlainAttr originalName = entityFactory.newEntity(GPlainAttr.class);
         originalName.setOwner(group);
         originalName.setSchema(plainSchemaDAO.find("originalName"));
-        originalName.add("syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
+        originalName.add(validator, "syncope's group", anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
         group.add(originalName);
 
         groupDAO.save(group);
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/RoleTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/RoleTest.java
index 402860d..b857573 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/RoleTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/RoleTest.java
@@ -32,6 +32,7 @@
 import javax.persistence.Query;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
@@ -71,6 +72,9 @@
     @Autowired
     private DelegationDAO delegationDAO;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     /**
      * Static copy of {@link org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO} method with same signature:
      * required for avoiding creating new transaction - good for general use case but bad for the way how
@@ -106,7 +110,7 @@
         UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
         attr.setOwner(user);
         attr.setSchema(plainSchemaDAO.find("cool"));
-        attr.add("true", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, "true", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(attr);
 
         user = userDAO.save(user);
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java
index 8f5c75a..46c35c8 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java
@@ -32,6 +32,7 @@
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.CipherAlgorithm;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidEntityException;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.ApplicationDAO;
 import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
@@ -96,6 +97,9 @@
     @Autowired
     private RoleDAO roleDAO;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     @Test
     public void delete() {
         List<UMembership> memberships = groupDAO.findUMemberships(groupDAO.findByName("managingDirector"));
@@ -169,7 +173,7 @@
         UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
         attr.setOwner(user);
         attr.setSchema(plainSchemaDAO.find("obscure"));
-        attr.add("testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, "testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(attr);
 
         // add 'obscure' to user (via 'artDirector' membership): does not work because 'obscure' is from 'other'
@@ -183,7 +187,7 @@
         attr.setOwner(user);
         attr.setMembership(membership);
         attr.setSchema(plainSchemaDAO.find("obscure"));
-        attr.add("testvalue2", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, "testvalue2", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(attr);
 
         try {
@@ -204,7 +208,7 @@
         UPlainAttr attr = entityFactory.newEntity(UPlainAttr.class);
         attr.setOwner(user);
         attr.setSchema(plainSchemaDAO.find("obscure"));
-        attr.add("testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, "testvalue", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(attr);
 
         // add 'obscure' (via 'additional' membership): that group defines type extension with classes 'other' and 'csv'
@@ -217,7 +221,7 @@
         attr.setOwner(user);
         attr.setMembership(membership);
         attr.setSchema(plainSchemaDAO.find("obscure"));
-        attr.add("testvalue2", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        attr.add(validator, "testvalue2", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(attr);
 
         userDAO.save(user);
@@ -260,7 +264,7 @@
         attr.setAccount(account);
         account.add(attr);
         attr.setSchema(plainSchemaDAO.find("obscure"));
-        attr.add("testvalue", anyUtils);
+        attr.add(validator, "testvalue", anyUtils);
 
         user = userDAO.save(user);
         entityManager().flush();
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index 0d41fc8..0acb0a1 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -632,7 +632,7 @@
                 connectorName="net.tirasa.connid.bundles.ldap.LdapConnector"
                 version="${connid.ldap.version}" 
                 jsonConf='[{"schema":{"name":"host","type":"java.lang.String","required":true,"order":1,"confidential":false,"defaultValues":[]},"values":["localhost"],"overridable":false},{"schema":{"name":"port","type":"int","required":false,"order":2,"confidential":false,"defaultValues":[389]},"values":[1389],"overridable":false},{"schema":{"name":"ssl","type":"boolean","required":false,"order":3,"confidential":false,"defaultValues":[false]},"values":["false"],"overridable":false},{"schema":{"name":"failover","type":"[Ljava.lang.String;","required":false,"order":4,"confidential":false,"defaultValues":[]},"values":[],"overridable":false},{"schema":{"name":"principal","type":"java.lang.String","required":false,"order":5,"confidential":false,"defaultValues":[]},"values":["uid=admin,ou=system"],"overridable":false},{"schema":{"name":"credentials","type":"org.identityconnectors.common.security.GuardedString","required":false,"order":6,"confidential":true,"defaultValues":[]},"values":["secret"],"overridable":false},{"schema":{"name":"baseContexts","type":"[Ljava.lang.String;","required":true,"order":7,"confidential":false,"defaultValues":[]},"values":["ou=people,o=isp","ou=groups,o=isp"],"overridable":true},{"schema":{"name":"passwordAttribute","type":"java.lang.String","required":false,"order":8,"confidential":false,"defaultValues":["userPassword"]},"values":["userpassword"],"overridable":false},{"schema":{"name":"accountObjectClasses","type":"[Ljava.lang.String;","required":false,"order":9,"confidential":false,"defaultValues":["top","person","organizationalPerson","inetOrgPerson"]},"values":["inetOrgPerson"],"overridable":false},{"schema":{"name":"accountUserNameAttributes","type":"[Ljava.lang.String;","required":false,"order":10,"confidential":false,"defaultValues":["uid","cn"]},"values":["uid"],"overridable":false},{"schema":{"name":"accountSearchFilter","type":"java.lang.String","required":false,"order":11,"confidential":false,"defaultValues":[]},"values":["uid=*"],"overridable":false},{"schema":{"name":"groupObjectClasses","type":"[Ljava.lang.String;","required":false,"order":12,"confidential":false,"defaultValues":["top","groupOfUniqueNames"]},"values":[],"overridable":false},{"schema":{"name":"groupNameAttributes","type":"[Ljava.lang.String;","required":false,"order":13,"confidential":false,"defaultValues":["cn"]},"values":["cn"],"overridable":false},{"schema":{"name":"groupMemberAttribute","type":"java.lang.String","required":false,"order":14,"confidential":false,"defaultValues":["uniqueMember"]},"values":[],"overridable":false},{"schema":{"name":"maintainLdapGroupMembership","type":"boolean","required":false,"order":15,"confidential":false,"defaultValues":[false]},"values":["true"],"overridable":false},{"schema":{"name":"maintainPosixGroupMembership","type":"boolean","required":false,"order":16,"confidential":false,"defaultValues":[false]},"values":["false"],"overridable":false},{"schema":{"name":"addPrincipalToNewGroups","type":"boolean","required":false,"order":17,"confidential":false,"defaultValues":[false]},"values":["true"],"overridable":false},{"schema":{"name":"passwordHashAlgorithm","type":"java.lang.String","required":false,"order":18,"confidential":false,"defaultValues":[]},"values":["SHA"],"overridable":false},{"schema":{"name":"respectResourcePasswordPolicyChangeAfterReset","type":"boolean","required":false,"order":19,"confidential":false,"defaultValues":[false]},"values":["false"],"overridable":false},{"schema":{"name":"useVlvControls","type":"boolean","required":false,"order":20,"confidential":false,"defaultValues":[false]},"values":[],"overridable":false},{"schema":{"name":"vlvSortAttribute","type":"java.lang.String","required":false,"order":21,"confidential":false,"defaultValues":["uid"]},"values":[],"overridable":false},{"schema":{"name":"uidAttribute","type":"java.lang.String","required":false,"order":22,"confidential":false,"defaultValues":["entryUUID"]},"values":["cn"],"overridable":true},{"schema":{"name":"gidAttribute","type":"java.lang.String","required":false,"order":23,"confidential":false,"defaultValues":["entryUUID"]},"values":["cn"],"overridable":true},{"schema":{"name":"readSchema","type":"boolean","required":false,"order":23,"confidential":false,"defaultValues":[true]},"values":["true"],"overridable":false},{"schema":{"name":"baseContextsToSynchronize","type":"[Ljava.lang.String;","required":false,"order":24,"confidential":false,"defaultValues":[]},"values":["ou=people,o=isp","ou=groups,o=isp"],"overridable":false},{"schema":{"name":"objectClassesToSynchronize","type":"[Ljava.lang.String;","required":false,"order":25,"confidential":false,"defaultValues":["inetOrgPerson"]},"values":["inetOrgPerson","groupOfUniqueNames"],"overridable":false},{"schema":{"name":"attributesToSynchronize","type":"[Ljava.lang.String;","required":false,"order":26,"confidential":false,"defaultValues":[]},"values":[],"overridable":false},{"schema":{"name":"modifiersNamesToFilterOut","type":"[Ljava.lang.String;","required":false,"order":27,"confidential":false,"defaultValues":[]},"values":[],"overridable":false},{"schema":{"name":"accountSynchronizationFilter","type":"java.lang.String","required":false,"order":28,"confidential":false,"defaultValues":[]},"values":[],"overridable":false},{"schema":{"name":"changeLogBlockSize","type":"int","required":false,"order":29,"confidential":false,"defaultValues":[100]},"values":[100],"overridable":false},{"schema":{"name":"changeNumberAttribute","type":"java.lang.String","required":false,"order":30,"confidential":false,"defaultValues":["changeNumber"]},"values":["changeNumber"],"overridable":false},{"schema":{"name":"filterWithOrInsteadOfAnd","type":"boolean","required":false,"order":31,"confidential":false,"defaultValues":[false]},"values":["false"],"overridable":false},{"schema":{"name":"removeLogEntryObjectClassFromFilter","type":"boolean","required":false,"order":32,"confidential":false,"defaultValues":[true]},"values":["false"],"overridable":false},{"schema":{"name":"synchronizePasswords","type":"boolean","required":false,"order":33,"confidential":false,"defaultValues":[false]},"values":["false"],"overridable":false},{"schema":{"name":"passwordAttributeToSynchronize","type":"java.lang.String","required":false,"order":34,"confidential":false,"defaultValues":[]},"values":[],"overridable":false},{"schema":{"name":"passwordDecryptionKey","type":"org.identityconnectors.common.security.GuardedByteArray","required":false,"order":35,"confidential":true,"defaultValues":[]},"values":[],"overridable":false},{"schema":{"name":"passwordDecryptionInitializationVector","type":"org.identityconnectors.common.security.GuardedByteArray","required":false,"order":36,"confidential":true,"defaultValues":[]},"values":[],"overridable":false},{"schema":{"name":"statusManagementClass","type":"java.lang.String","required":false,"order":37,"confidential":false,"defaultValues":[]},"values":["net.tirasa.connid.bundles.ldap.commons.AttributeStatusManagement"],"overridable":false},{"schema":{"name":"retrievePasswordsWithSearch","type":"boolean","required":false,"order":38,"confidential":false,"defaultValues":[false]},"values":[],"overridable":false},{"schema":{"name":"dnAttribute","type":"java.lang.String","required":false,"order":39,"confidential":false,"defaultValues":["entryDN"]},"values":[],"overridable":false},{"schema":{"name":"groupSearchFilter","type":"java.lang.String","required":false,"order":40,"confidential":false,"defaultValues":[]},"values":[],"overridable":false},{"schema":{"name":"readTimeout","type":"long","required":false,"order":41,"confidential":false,"defaultValues":[0]},"values":[],"overridable":false},{"schema":{"name":"connectTimeout","type":"long","required":false,"order":42,"confidential":false,"defaultValues":[0]},"values":[],"overridable":false}]'
-                capabilities='["CREATE","UPDATE","DELETE","SEARCH"]'/>
+                capabilities='["CREATE","UPDATE","UPDATE_DELTA","DELETE","SEARCH"]'/>
   
   <ConnInstance id="a28abd9b-9f4a-4ef6-a7a8-d19ad2a8f29d" displayName="H2-test2"
                 adminRealm_id="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28"
@@ -657,7 +657,7 @@
                 connectorName="net.tirasa.connid.bundles.db.scriptedsql.ScriptedSQLConnector"
                 displayName="Scripted SQL" version="${connid.database.version}"
                 jsonConf='[{&quot;schema&quot;:{&quot;name&quot;:&quot;updateScriptFileName&quot;,&quot;displayName&quot;:&quot;updateScriptFileName&quot;,&quot;helpMessage&quot;:&quot;updateScriptFileName&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;${conf.directory}/scriptedsql/UpdateScript.groovy&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;testScript&quot;,&quot;displayName&quot;:&quot;testScript&quot;,&quot;helpMessage&quot;:&quot;testScript&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;host&quot;,&quot;displayName&quot;:&quot;Host&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;Host&lt;/b&gt;&lt;br/&gt;Enter the name of the host where the database is running.&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:2,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;localhost&quot;]},&quot;overridable&quot;:false},{&quot;schema&quot;:{&quot;name&quot;:&quot;port&quot;,&quot;displayName&quot;:&quot;Port&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;TCP Port&lt;/b&gt;&lt;br/&gt;Enter the port number the database server is listening on.&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:3,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;3306&quot;]},&quot;overridable&quot;:false},{&quot;schema&quot;:{&quot;name&quot;:&quot;database&quot;,&quot;displayName&quot;:&quot;Database&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;Database&lt;/b&gt;&lt;br/&gt;Enter the name of the database on the database server that contains the table.&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:6,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false},{&quot;schema&quot;:{&quot;name&quot;:&quot;createScript&quot;,&quot;displayName&quot;:&quot;createScript&quot;,&quot;helpMessage&quot;:&quot;createScript&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;jdbcUrlTemplate&quot;,&quot;displayName&quot;:&quot;JDBC Connection URL&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;JDBC Connection URL&lt;/b&gt;&lt;br/&gt;Specify the JDBC Driver Connection URL.&lt;br/&gt; Oracle template is jdbc:oracle:thin:@[host]:[port(1521)]:[DB].&lt;br/&gt;  MySQL template is jdbc:mysql://[host]:[port(3306)]/[db], for more info, read the JDBC driver documentation.&lt;br/&gt;Could be empty if datasource is provided.&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:11,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;jdbc:mysql://%h:%p/%d&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;${testdb.url}&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;jndiProperties&quot;,&quot;displayName&quot;:&quot;Initial JNDI Properties&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;Initial JNDI Properties&lt;/b&gt;&lt;br/&gt;Could be empty or enter the JDBC JNDI Initial context factory, context provider in a format: key = value.&quot;,&quot;type&quot;:&quot;[Ljava.lang.String;&quot;,&quot;required&quot;:false,&quot;order&quot;:21,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;enableEmptyString&quot;,&quot;displayName&quot;:&quot;Enable writing empty string&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;Enable writing empty string&lt;/b&gt;&lt;br/&gt;Select to enable support for writing an empty strings, instead of a NULL value, in character based columns defined as not-null in the table schema. This option does not influence the way strings are written for Oracle based tables. By default empty strings are written as a NULL value.&quot;,&quot;type&quot;:&quot;boolean&quot;,&quot;required&quot;:false,&quot;order&quot;:12,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[false]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;false&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;allNative&quot;,&quot;displayName&quot;:&quot;All native&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;All native&lt;/b&gt;&lt;br/&gt;Select to retrieve all data type of the columns in a native format from the database table.&quot;,&quot;type&quot;:&quot;boolean&quot;,&quot;required&quot;:false,&quot;order&quot;:16,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[false]},&quot;overridable&quot;:false,&quot;values&quot;:[false]},{&quot;schema&quot;:{&quot;name&quot;:&quot;password&quot;,&quot;displayName&quot;:&quot;User Password&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;User Password&lt;/b&gt;&lt;br/&gt;Enter a user account that has permission to access accounts table.&quot;,&quot;type&quot;:&quot;org.identityconnectors.common.security.GuardedString&quot;,&quot;required&quot;:false,&quot;order&quot;:5,&quot;confidential&quot;:true,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;${testdb.password}&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;validConnectionQuery&quot;,&quot;displayName&quot;:&quot;Validate Connection Query&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;Validate Connection Query&lt;/b&gt;&lt;br/&gt;There can be specified the check connection alive query. If empty, default implementation will test it using the switch on/off the autocommit. Some select 1 from dummy table could be more efficient.&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:17,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;reloadScriptOnExecution&quot;,&quot;displayName&quot;:&quot;reloadScriptOnExecution&quot;,&quot;helpMessage&quot;:&quot;reloadScriptOnExecution&quot;,&quot;type&quot;:&quot;boolean&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[false]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;true&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;schemaScriptFileName&quot;,&quot;displayName&quot;:&quot;schemaScriptFileName&quot;,&quot;helpMessage&quot;:&quot;schemaScriptFileName&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:true,&quot;values&quot;:[&quot;${conf.directory}/scriptedsql/SchemaScript.groovy&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;jdbcDriver&quot;,&quot;displayName&quot;:&quot;JDBC Driver&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;JDBC Driver&lt;/b&gt;&lt;br/&gt;Specify the JDBC Driver class name. Oracle is oracle.jdbc.driver.OracleDriver. MySQL is org.gjt.mm.mysql.Driver.&lt;br/&gt;Could be empty if datasource is provided.&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:10,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;com.mysql.jdbc.Driver&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;${testdb.driver}&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;testScriptFileName&quot;,&quot;displayName&quot;:&quot;testScriptFileName&quot;,&quot;helpMessage&quot;:&quot;testScriptFileName&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:true,&quot;values&quot;:[&quot;${conf.directory}/scriptedsql/TestScript.groovy&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;quoting&quot;,&quot;displayName&quot;:&quot;Name Quoting&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;Name Quoting&lt;/b&gt;&lt;br/&gt;Select whether database column names for this resource should be quoted, and the quoting characters. By default, database column names are not quoted (None). For other selections (Single, Double, Back, or Brackets), column names will appear between single quotes, double quotes, back quotes, or brackets in the SQL generated to access the database.&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:-1,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;createScriptFileName&quot;,&quot;displayName&quot;:&quot;createScriptFileName&quot;,&quot;helpMessage&quot;:&quot;createScriptFileName&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;${conf.directory}/scriptedsql/CreateScript.groovy&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;clearTextPasswordToScript&quot;,&quot;displayName&quot;:&quot;clearTextPasswordToScript&quot;,&quot;helpMessage&quot;:&quot;clearTextPasswordToScript&quot;,&quot;type&quot;:&quot;boolean&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[true]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;false&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;nativeTimestamps&quot;,&quot;displayName&quot;:&quot;Native Timestamps&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;Native Timestamps&lt;/b&gt;&lt;br/&gt;Select to retrieve Timestamp data type of the columns in java.sql.Timestamp format from the database table.&quot;,&quot;type&quot;:&quot;boolean&quot;,&quot;required&quot;:false,&quot;order&quot;:15,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[false]},&quot;overridable&quot;:false,&quot;values&quot;:[false]},{&quot;schema&quot;:{&quot;name&quot;:&quot;syncScript&quot;,&quot;displayName&quot;:&quot;syncScript&quot;,&quot;helpMessage&quot;:&quot;syncScript&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;autoCommit&quot;,&quot;displayName&quot;:&quot;autoCommit&quot;,&quot;helpMessage&quot;:&quot;autoCommit&quot;,&quot;type&quot;:&quot;boolean&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[true]},&quot;overridable&quot;:false,&quot;values&quot;:[true]},{&quot;schema&quot;:{&quot;name&quot;:&quot;scriptingLanguage&quot;,&quot;displayName&quot;:&quot;scriptingLanguage&quot;,&quot;helpMessage&quot;:&quot;scriptingLanguage&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;GROOVY&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;GROOVY&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;datasource&quot;,&quot;displayName&quot;:&quot;Datasource Path&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;JDBC Data Source Name/Path&lt;/b&gt;&lt;br/&gt;Enter the JDBC Data Source Name/Path to connect to the Oracle server. If specified, connector will only try to connect using Datasource and ignore other resource parameters specified.&lt;br/&gt;the example value is: &lt;CODE&gt;jdbc/SampleDataSourceName&lt;/CODE&gt;&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:20,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;deleteScript&quot;,&quot;displayName&quot;:&quot;deleteScript&quot;,&quot;helpMessage&quot;:&quot;deleteScript&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;rethrowAllSQLExceptions&quot;,&quot;displayName&quot;:&quot;Rethrow all SQLExceptions&quot;,&quot;helpMessage&quot;:&quot;If this is not checked, SQL statements which throw SQLExceptions with a 0 ErrorCode will be have the exception caught and suppressed. Check it to have exceptions with 0 ErrorCodes rethrown.&quot;,&quot;type&quot;:&quot;boolean&quot;,&quot;required&quot;:false,&quot;order&quot;:14,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[true]},&quot;overridable&quot;:false,&quot;values&quot;:[true]},{&quot;schema&quot;:{&quot;name&quot;:&quot;syncScriptFileName&quot;,&quot;displayName&quot;:&quot;syncScriptFileName&quot;,&quot;helpMessage&quot;:&quot;syncScriptFileName&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:true,&quot;values&quot;:[&quot;${conf.directory}/scriptedsql/SyncScript.groovy&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;updateScript&quot;,&quot;displayName&quot;:&quot;updateScript&quot;,&quot;helpMessage&quot;:&quot;updateScript&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[]},{&quot;schema&quot;:{&quot;name&quot;:&quot;user&quot;,&quot;displayName&quot;:&quot;User&quot;,&quot;helpMessage&quot;:&quot;&lt;b&gt;User&lt;/b&gt;&lt;br/&gt;Enter the name of the mandatory Database user with permission to account table.&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:4,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;${testdb.username}&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;deleteScriptFileName&quot;,&quot;displayName&quot;:&quot;deleteScriptFileName&quot;,&quot;helpMessage&quot;:&quot;deleteScriptFileName&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:false,&quot;values&quot;:[&quot;${conf.directory}/scriptedsql/DeleteScript.groovy&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;searchScriptFileName&quot;,&quot;displayName&quot;:&quot;searchScriptFileName&quot;,&quot;helpMessage&quot;:&quot;searchScriptFileName&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[]},&quot;overridable&quot;:true,&quot;values&quot;:[&quot;${conf.directory}/scriptedsql/SearchScript.groovy&quot;]},{&quot;schema&quot;:{&quot;name&quot;:&quot;searchScript&quot;,&quot;displayName&quot;:&quot;searchScript&quot;,&quot;helpMessage&quot;:&quot;searchScript&quot;,&quot;type&quot;:&quot;java.lang.String&quot;,&quot;required&quot;:false,&quot;order&quot;:0,&quot;confidential&quot;:false,&quot;defaultValues&quot;:[&quot;&quot;]},&quot;overridable&quot;:false,&quot;values&quot;:[]}]'
-                capabilities='["CREATE","UPDATE","DELETE","SEARCH","SYNC"]'/>
+                capabilities='["CREATE","UPDATE","UPDATE_DELTA","DELETE","SEARCH","SYNC"]'/>
   
   <ConnInstance id="44c02549-19c3-483c-8025-4919c3283c37" bundlename="net.tirasa.connid.bundles.rest"
                 adminRealm_id="0679e069-7355-4b20-bd11-a5a0a5453c7c"
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
index d03331d..68cc8a5 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
@@ -236,7 +236,7 @@
 
         Set<AttributeDelta> result = null;
 
-        if (connInstance.getCapabilities().contains(ConnectorCapability.UPDATE)) {
+        if (connInstance.getCapabilities().contains(ConnectorCapability.UPDATE_DELTA)) {
             propagationAttempted.set(true);
 
             Future<Set<AttributeDelta>> future = 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
index 36a44df..5554f89 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
@@ -34,6 +34,7 @@
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.syncope.common.lib.LogOutputStream;
 import org.apache.syncope.core.persistence.api.DomainHolder;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyMatchDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
@@ -624,7 +625,8 @@
             final NotificationManager notificationManager,
             final AuditManager auditManager,
             final TaskDataBinder taskDataBinder,
-            final OutboundMatcher outboundMatcher) {
+            final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator) {
 
         return new PriorityPropagationTaskExecutor(
                 connectorManager,
@@ -642,6 +644,7 @@
                 taskUtilsFactory,
                 entityFactory,
                 outboundMatcher,
+                validator,
                 propagationTaskExecutorAsyncExecutor);
     }
 
@@ -837,7 +840,8 @@
             final VirAttrHandler virAttrHandler,
             final MappingManager mappingManager,
             final IntAttrNameParser intAttrNameParser,
-            final OutboundMatcher outboundMatcher) {
+            final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator) {
 
         return new AnyObjectDataBinderImpl(
                 anyTypeDAO,
@@ -857,7 +861,8 @@
                 virAttrHandler,
                 mappingManager,
                 intAttrNameParser,
-                outboundMatcher);
+                outboundMatcher,
+                validator);
     }
 
     @ConditionalOnMissingBean
@@ -995,7 +1000,8 @@
             final VirAttrHandler virAttrHandler,
             final MappingManager mappingManager,
             final IntAttrNameParser intAttrNameParser,
-            final OutboundMatcher outboundMatcher) {
+            final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator) {
 
         return new GroupDataBinderImpl(
                 anyTypeDAO,
@@ -1016,7 +1022,8 @@
                 mappingManager,
                 intAttrNameParser,
                 outboundMatcher,
-                searchCondVisitor);
+                searchCondVisitor,
+                validator);
     }
 
     @ConditionalOnMissingBean
@@ -1237,6 +1244,7 @@
             final MappingManager mappingManager,
             final IntAttrNameParser intAttrNameParser,
             final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator,
             final RoleDAO roleDAO,
             final SecurityQuestionDAO securityQuestionDAO,
             final ApplicationDAO applicationDAO,
@@ -1263,6 +1271,7 @@
                 mappingManager,
                 intAttrNameParser,
                 outboundMatcher,
+                validator,
                 roleDAO,
                 securityQuestionDAO,
                 applicationDAO,
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
index 65d7100..b728ec7 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
@@ -47,6 +47,7 @@
 import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AllowedSchemas;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
@@ -135,6 +136,8 @@
 
     protected final OutboundMatcher outboundMatcher;
 
+    protected final PlainAttrValidationManager validator;
+
     protected AbstractAnyDataBinder(
             final AnyTypeDAO anyTypeDAO,
             final RealmDAO realmDAO,
@@ -153,7 +156,8 @@
             final VirAttrHandler virAttrHandler,
             final MappingManager mappingManager,
             final IntAttrNameParser intAttrNameParser,
-            final OutboundMatcher outboundMatcher) {
+            final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator) {
 
         this.anyTypeDAO = anyTypeDAO;
         this.realmDAO = realmDAO;
@@ -173,6 +177,7 @@
         this.mappingManager = mappingManager;
         this.intAttrNameParser = intAttrNameParser;
         this.outboundMatcher = outboundMatcher;
+        this.validator = validator;
     }
 
     protected void setRealm(final Any<?> any, final AnyUR anyUR) {
@@ -260,7 +265,7 @@
                 LOG.debug("Null value for {}, ignoring", schema.getKey());
             } else {
                 try {
-                    attr.add(value, anyUtils);
+                    attr.add(validator, value, anyUtils);
                 } catch (InvalidPlainAttrValueException e) {
                     String valueToPrint = value.length() > 40
                             ? value.substring(0, 20) + "..."
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
index 11f9b1d..643f2e7 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
@@ -39,6 +39,7 @@
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
@@ -93,7 +94,8 @@
             final VirAttrHandler virAttrHandler,
             final MappingManager mappingManager,
             final IntAttrNameParser intAttrNameParser,
-            final OutboundMatcher outboundMatcher) {
+            final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator) {
 
         super(anyTypeDAO,
                 realmDAO,
@@ -112,7 +114,8 @@
                 virAttrHandler,
                 mappingManager,
                 intAttrNameParser,
-                outboundMatcher);
+                outboundMatcher,
+                validator);
     }
 
     @Transactional(readOnly = true)
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
index d33fd4e..b49e90a 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
@@ -35,6 +35,7 @@
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
@@ -96,7 +97,8 @@
             final MappingManager mappingManager,
             final IntAttrNameParser intAttrNameParser,
             final OutboundMatcher outboundMatcher,
-            final SearchCondVisitor searchCondVisitor) {
+            final SearchCondVisitor searchCondVisitor,
+            final PlainAttrValidationManager validator) {
 
         super(anyTypeDAO,
                 realmDAO,
@@ -115,7 +117,8 @@
                 virAttrHandler,
                 mappingManager,
                 intAttrNameParser,
-                outboundMatcher);
+                outboundMatcher,
+                validator);
 
         this.searchCondVisitor = searchCondVisitor;
     }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java
index d5b6554..c7dad2e 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java
@@ -28,7 +28,7 @@
 import org.apache.syncope.common.lib.types.IdMImplementationType;
 import org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.syncope.common.lib.types.ImplementationEngine;
-import org.apache.syncope.core.persistence.api.attrvalue.validation.Validator;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValueValidator;
 import org.apache.syncope.core.persistence.api.dao.AccountRule;
 import org.apache.syncope.core.persistence.api.dao.PasswordRule;
 import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule;
@@ -138,7 +138,7 @@
                     break;
 
                 case IdRepoImplementationType.VALIDATOR:
-                    base = Validator.class;
+                    base = PlainAttrValueValidator.class;
                     break;
 
                 case IdRepoImplementationType.RECIPIENTS_PROVIDER:
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
index c6010e8..b86f460 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
@@ -352,10 +352,10 @@
         resource.setProvisioningTraceLevel(resourceTO.getProvisioningTraceLevel());
 
         resource.setPasswordPolicy(resourceTO.getPasswordPolicy() == null
-                ? null : (PasswordPolicy) policyDAO.find(resourceTO.getPasswordPolicy()));
+                ? null : policyDAO.<PasswordPolicy>find(resourceTO.getPasswordPolicy()));
 
         resource.setAccountPolicy(resourceTO.getAccountPolicy() == null
-                ? null : (AccountPolicy) policyDAO.find(resourceTO.getAccountPolicy()));
+                ? null : policyDAO.<AccountPolicy>find(resourceTO.getAccountPolicy()));
 
         if (resource.getPropagationPolicy() != null
                 && !resource.getPropagationPolicy().getKey().equals(resourceTO.getPropagationPolicy())) {
@@ -363,13 +363,13 @@
             propagationTaskExecutor.expireRetryTemplate(resource.getKey());
         }
         resource.setPropagationPolicy(resourceTO.getPropagationPolicy() == null
-                ? null : (PropagationPolicy) policyDAO.find(resourceTO.getPropagationPolicy()));
+                ? null : policyDAO.<PropagationPolicy>find(resourceTO.getPropagationPolicy()));
 
         resource.setPullPolicy(resourceTO.getPullPolicy() == null
-                ? null : (PullPolicy) policyDAO.find(resourceTO.getPullPolicy()));
+                ? null : policyDAO.<PullPolicy>find(resourceTO.getPullPolicy()));
 
         resource.setPushPolicy(resourceTO.getPushPolicy() == null
-                ? null : (PushPolicy) policyDAO.find(resourceTO.getPushPolicy()));
+                ? null : policyDAO.<PushPolicy>find(resourceTO.getPushPolicy()));
 
         if (resourceTO.getProvisionSorter() == null) {
             resource.setProvisionSorter(null);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
index 719c650..15be775 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
@@ -48,6 +48,7 @@
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AccessTokenDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
@@ -131,6 +132,7 @@
             final MappingManager mappingManager,
             final IntAttrNameParser intAttrNameParser,
             final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator,
             final RoleDAO roleDAO,
             final SecurityQuestionDAO securityQuestionDAO,
             final ApplicationDAO applicationDAO,
@@ -156,7 +158,8 @@
                 virAttrHandler,
                 mappingManager,
                 intAttrNameParser,
-                outboundMatcher);
+                outboundMatcher,
+                validator);
 
         this.roleDAO = roleDAO;
         this.securityQuestionDAO = securityQuestionDAO;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java
index 5705deb..f16b5d0 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java
@@ -121,6 +121,8 @@
 
     protected final SearchCondVisitor searchCondVisitor;
 
+    protected Optional<RecipientsProvider> perContextRecipientsProvider = Optional.empty();
+
     public DefaultNotificationManager(
             final DerSchemaDAO derSchemaDAO,
             final VirSchemaDAO virSchemaDAO,
@@ -216,8 +218,11 @@
 
         if (notification.getRecipientsProvider() != null) {
             try {
-                RecipientsProvider recipientsProvider =
-                        ImplementationManager.build(notification.getRecipientsProvider());
+                RecipientsProvider recipientsProvider = ImplementationManager.build(
+                        notification.getRecipientsProvider(),
+                        () -> perContextRecipientsProvider.orElse(null),
+                        instance -> perContextRecipientsProvider = Optional.of(instance));
+
                 recipientEmails.addAll(recipientsProvider.provideRecipients(notification));
             } catch (Exception e) {
                 LOG.error("While building {}", notification.getRecipientsProvider(), e);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
index 2db1e6e..f4fcd7a 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
@@ -27,6 +27,7 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -39,6 +40,7 @@
 import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.TraceLevel;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -127,6 +129,10 @@
 
     protected final OutboundMatcher outboundMatcher;
 
+    protected final PlainAttrValidationManager validator;
+
+    protected final Map<String, PropagationActions> perContextActions = new ConcurrentHashMap<>();
+
     public AbstractPropagationTaskExecutor(
             final ConnectorManager connectorManager,
             final ConnObjectUtils connObjectUtils,
@@ -142,7 +148,8 @@
             final AnyUtilsFactory anyUtilsFactory,
             final TaskUtilsFactory taskUtilsFactory,
             final EntityFactory entityFactory,
-            final OutboundMatcher outboundMatcher) {
+            final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator) {
 
         this.connectorManager = connectorManager;
         this.connObjectUtils = connObjectUtils;
@@ -159,6 +166,7 @@
         this.taskUtilsFactory = taskUtilsFactory;
         this.entityFactory = entityFactory;
         this.outboundMatcher = outboundMatcher;
+        this.validator = validator;
     }
 
     @Override
@@ -171,7 +179,10 @@
 
         resource.getPropagationActions().forEach(impl -> {
             try {
-                result.add(ImplementationManager.build(impl));
+                result.add(ImplementationManager.build(
+                        impl,
+                        () -> perContextActions.get(impl.getKey()),
+                        instance -> perContextActions.put(impl.getKey(), instance)));
             } catch (Exception e) {
                 LOG.error("While building {}", impl, e);
             }
@@ -193,7 +204,10 @@
         taskInfo.getResource().getProvision(taskInfo.getAnyType()).
                 filter(provision -> provision.getUidOnCreate() != null).
                 ifPresent(provision -> anyUtilsFactory.getInstance(taskInfo.getAnyTypeKind()).addAttr(
-                taskInfo.getEntityKey(), plainSchemaDAO.find(provision.getUidOnCreate()), result.getUidValue()));
+                validator,
+                taskInfo.getEntityKey(),
+                plainSchemaDAO.find(provision.getUidOnCreate()),
+                result.getUidValue()));
 
         return result;
     }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AzurePropagationActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AzurePropagationActions.java
index 31fec8e..c90a0e9 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AzurePropagationActions.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AzurePropagationActions.java
@@ -24,6 +24,8 @@
 import org.apache.syncope.core.persistence.api.entity.task.PropagationData;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
+import org.apache.syncope.core.spring.implementation.InstanceScope;
+import org.apache.syncope.core.spring.implementation.SyncopeImplementation;
 import org.identityconnectors.framework.common.objects.Attribute;
 import org.identityconnectors.framework.common.objects.AttributeUtil;
 import org.identityconnectors.framework.common.objects.Name;
@@ -37,6 +39,7 @@
  *
  * It ensures to send the configured e-mail address as {@code __NAME__}.
  */
+@SyncopeImplementation(scope = InstanceScope.PER_CONTEXT)
 public class AzurePropagationActions implements PropagationActions {
 
     protected static final Logger LOG = LoggerFactory.getLogger(AzurePropagationActions.class);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DBPasswordPropagationActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DBPasswordPropagationActions.java
index a0e154f..e36a73e 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DBPasswordPropagationActions.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DBPasswordPropagationActions.java
@@ -30,6 +30,8 @@
 import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
+import org.apache.syncope.core.spring.implementation.InstanceScope;
+import org.apache.syncope.core.spring.implementation.SyncopeImplementation;
 import org.identityconnectors.common.security.GuardedString;
 import org.identityconnectors.framework.common.objects.Attribute;
 import org.identityconnectors.framework.common.objects.AttributeBuilder;
@@ -43,6 +45,7 @@
  * added a password. The CipherAlgorithm associated with the password must match the password
  * cipher algorithm property of the DB Connector.
  */
+@SyncopeImplementation(scope = InstanceScope.PER_CONTEXT)
 public class DBPasswordPropagationActions implements PropagationActions {
 
     protected static final String CLEARTEXT = "CLEARTEXT";
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java
index 39ede6a..6bb431b 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java
@@ -18,9 +18,6 @@
  */
 package org.apache.syncope.core.provisioning.java.propagation;
 
-import static org.apache.syncope.core.provisioning.api.propagation.PropagationManager.MANDATORY_MISSING_ATTR_NAME;
-import static org.apache.syncope.core.provisioning.api.propagation.PropagationManager.MANDATORY_NULL_OR_EMPTY_ATTR_NAME;
-
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/GoogleAppsPropagationActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/GoogleAppsPropagationActions.java
index 846994d..d1a869f 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/GoogleAppsPropagationActions.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/GoogleAppsPropagationActions.java
@@ -25,6 +25,8 @@
 import org.apache.syncope.core.persistence.api.entity.task.PropagationData;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
+import org.apache.syncope.core.spring.implementation.InstanceScope;
+import org.apache.syncope.core.spring.implementation.SyncopeImplementation;
 import org.identityconnectors.framework.common.objects.Attribute;
 import org.identityconnectors.framework.common.objects.AttributeUtil;
 import org.identityconnectors.framework.common.objects.Name;
@@ -38,6 +40,7 @@
  *
  * It ensures to send the configured e-mail address as {@code __NAME__}.
  */
+@SyncopeImplementation(scope = InstanceScope.PER_CONTEXT)
 public class GoogleAppsPropagationActions implements PropagationActions {
 
     protected static final Logger LOG = LoggerFactory.getLogger(GoogleAppsPropagationActions.class);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
index bb590cc..9cb4215 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
@@ -43,6 +43,8 @@
 import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
+import org.apache.syncope.core.spring.implementation.InstanceScope;
+import org.apache.syncope.core.spring.implementation.SyncopeImplementation;
 import org.identityconnectors.framework.common.objects.AttributeBuilder;
 import org.identityconnectors.framework.common.objects.AttributeDeltaBuilder;
 import org.identityconnectors.framework.common.objects.AttributeDeltaUtil;
@@ -58,6 +60,7 @@
  *
  * @see org.apache.syncope.core.provisioning.java.pushpull.LDAPMembershipPullActions
  */
+@SyncopeImplementation(scope = InstanceScope.PER_CONTEXT)
 public class LDAPMembershipPropagationActions implements PropagationActions {
 
     protected static final Logger LOG = LoggerFactory.getLogger(LDAPMembershipPropagationActions.class);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPPasswordPropagationActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPPasswordPropagationActions.java
index 76ab435..eb3cee8 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPPasswordPropagationActions.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPPasswordPropagationActions.java
@@ -30,6 +30,8 @@
 import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationManager;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
+import org.apache.syncope.core.spring.implementation.InstanceScope;
+import org.apache.syncope.core.spring.implementation.SyncopeImplementation;
 import org.identityconnectors.common.security.GuardedString;
 import org.identityconnectors.framework.common.objects.Attribute;
 import org.identityconnectors.framework.common.objects.AttributeBuilder;
@@ -43,6 +45,7 @@
  * added a password. The CipherAlgorithm associated with the password must match the password
  * hash algorithm property of the LDAP Connector.
  */
+@SyncopeImplementation(scope = InstanceScope.PER_CONTEXT)
 public class LDAPPasswordPropagationActions implements PropagationActions {
 
     private static final String CLEARTEXT = "CLEARTEXT";
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
index 5daf4bf..5fb86aa 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java
@@ -28,6 +28,7 @@
 import java.util.concurrent.Future;
 import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.types.ExecStatus;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -102,6 +103,7 @@
             final TaskUtilsFactory taskUtilsFactory,
             final EntityFactory entityFactory,
             final OutboundMatcher outboundMatcher,
+            final PlainAttrValidationManager validator,
             final ThreadPoolTaskExecutor taskExecutor) {
 
         super(connectorManager,
@@ -118,7 +120,8 @@
                 anyUtilsFactory,
                 taskUtilsFactory,
                 entityFactory,
-                outboundMatcher);
+                outboundMatcher,
+                validator);
         this.taskExecutor = taskExecutor;
     }
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractProvisioningJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractProvisioningJobDelegate.java
index fe113c6..c429d09 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractProvisioningJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractProvisioningJobDelegate.java
@@ -23,6 +23,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.to.Mapping;
 import org.apache.syncope.common.lib.to.Provision;
@@ -36,8 +37,10 @@
 import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
 import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.ConnectorManager;
+import org.apache.syncope.core.provisioning.api.ProvisionSorter;
 import org.apache.syncope.core.provisioning.java.job.AbstractSchedTaskJobDelegate;
 import org.apache.syncope.core.provisioning.java.job.TaskJob;
+import org.apache.syncope.core.spring.ImplementationManager;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobExecutionException;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -105,6 +108,26 @@
     @Autowired
     protected PolicyDAO policyDAO;
 
+    protected Optional<ProvisionSorter> perContextProvisionSorter = Optional.empty();
+
+    protected ProvisionSorter getProvisionSorter(final T task) {
+        if (task.getResource().getProvisionSorter() != null) {
+            try {
+                return ImplementationManager.build(
+                        task.getResource().getProvisionSorter(),
+                        () -> perContextProvisionSorter.orElse(null),
+                        instance -> perContextProvisionSorter = Optional.of(instance));
+            } catch (Exception e) {
+                LOG.error("While building {}", task.getResource().getProvisionSorter(), e);
+            }
+        }
+
+        if (perContextProvisionSorter.isEmpty()) {
+            perContextProvisionSorter = Optional.of(new DefaultProvisionSorter());
+        }
+        return perContextProvisionSorter.get();
+    }
+
     /**
      * Create a textual report of the provisioning operation, based on the trace level.
      *
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java
index 8ca7fae..dd5af11 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java
@@ -21,8 +21,10 @@
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.syncope.common.lib.to.Item;
@@ -106,6 +108,8 @@
 
     protected final AnyUtilsFactory anyUtilsFactory;
 
+    protected final Map<String, PullCorrelationRule> perContextPullCorrelationRules = new ConcurrentHashMap<>();
+
     public InboundMatcher(
             final UserDAO userDAO,
             final AnyObjectDAO anyObjectDAO,
@@ -130,13 +134,6 @@
         this.anyUtilsFactory = anyUtilsFactory;
     }
 
-    protected List<Implementation> getTransformers(final Item item) {
-        return item.getTransformers().stream().
-                map(implementationDAO::find).
-                filter(Objects::nonNull).
-                collect(Collectors.toList());
-    }
-
     public Optional<PullMatch> match(
             final AnyType anyType,
             final String nameValue,
@@ -212,6 +209,13 @@
         return result;
     }
 
+    protected List<Implementation> getTransformers(final Item item) {
+        return item.getTransformers().stream().
+                map(implementationDAO::find).
+                filter(Objects::nonNull).
+                collect(Collectors.toList());
+    }
+
     public List<PullMatch> matchByConnObjectKeyValue(
             final Item connObjectKeyItem,
             final String connObjectKeyValue,
@@ -353,6 +357,27 @@
         return result;
     }
 
+    protected Optional<PullCorrelationRule> rule(final ExternalResource resource, final Provision provision) {
+        Optional<? extends PullCorrelationRuleEntity> correlationRule = resource.getPullPolicy() == null
+                ? Optional.empty()
+                : resource.getPullPolicy().getCorrelationRule(provision.getAnyType());
+
+        Optional<PullCorrelationRule> rule = Optional.empty();
+        if (correlationRule.isPresent()) {
+            Implementation impl = correlationRule.get().getImplementation();
+            try {
+                rule = ImplementationManager.buildPullCorrelationRule(
+                        impl,
+                        () -> perContextPullCorrelationRules.get(impl.getKey()),
+                        instance -> perContextPullCorrelationRules.put(impl.getKey(), instance));
+            } catch (Exception e) {
+                LOG.error("While building {}", impl, e);
+            }
+        }
+
+        return rule;
+    }
+
     /**
      * Finds internal entities based on external attributes and mapping.
      *
@@ -368,18 +393,7 @@
             final Provision provision,
             final AnyTypeKind anyTypeKind) {
 
-        Optional<? extends PullCorrelationRuleEntity> correlationRule = resource.getPullPolicy() == null
-                ? Optional.empty()
-                : resource.getPullPolicy().getCorrelationRule(provision.getAnyType());
-
-        Optional<PullCorrelationRule> rule = Optional.empty();
-        if (correlationRule.isPresent()) {
-            try {
-                rule = ImplementationManager.buildPullCorrelationRule(correlationRule.get().getImplementation());
-            } catch (Exception e) {
-                LOG.error("While building {}", correlationRule.get().getImplementation(), e);
-            }
-        }
+        Optional<PullCorrelationRule> rule = rule(resource, provision);
 
         List<PullMatch> result = List.of();
         try {
@@ -470,7 +484,7 @@
 
             case "name":
                 if (orgUnit.isIgnoreCaseMatch()) {
-                    final String realmName = connObjectKey;
+                    String realmName = connObjectKey;
                     result.addAll(realmDAO.findAll().stream().
                             filter(r -> r.getName().equalsIgnoreCase(realmName)).collect(Collectors.toList()));
                 } else {
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java
index 89da2ee..2c78a12 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/OutboundMatcher.java
@@ -22,8 +22,10 @@
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Stream;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.syncope.common.lib.to.Item;
@@ -35,6 +37,7 @@
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
 import org.apache.syncope.core.provisioning.api.Connector;
@@ -69,6 +72,10 @@
 
     protected final VirAttrHandler virAttrHandler;
 
+    protected final Map<String, PropagationActions> perContextActions = new ConcurrentHashMap<>();
+
+    protected final Map<String, PushCorrelationRule> perContextPushCorrelationRules = new ConcurrentHashMap<>();
+
     public OutboundMatcher(
             final MappingManager mappingManager,
             final UserDAO userDAO,
@@ -90,10 +97,14 @@
 
         Optional<PushCorrelationRule> rule = Optional.empty();
         if (correlationRule.isPresent()) {
+            Implementation impl = correlationRule.get().getImplementation();
             try {
-                rule = ImplementationManager.buildPushCorrelationRule(correlationRule.get().getImplementation());
+                rule = ImplementationManager.buildPushCorrelationRule(
+                        impl,
+                        () -> perContextPushCorrelationRules.get(impl.getKey()),
+                        instance -> perContextPushCorrelationRules.put(impl.getKey(), instance));
             } catch (Exception e) {
-                LOG.error("While building {}", correlationRule.get().getImplementation(), e);
+                LOG.error("While building {}", impl, e);
             }
         }
 
@@ -160,6 +171,23 @@
         return result;
     }
 
+    protected List<PropagationActions> getPropagationActions(final ExternalResource resource) {
+        List<PropagationActions> result = new ArrayList<>();
+
+        resource.getPropagationActions().forEach(impl -> {
+            try {
+                result.add(ImplementationManager.build(
+                        impl,
+                        () -> perContextActions.get(impl.getKey()),
+                        instance -> perContextActions.put(impl.getKey(), instance)));
+            } catch (Exception e) {
+                LOG.error("While building {}", impl, e);
+            }
+        });
+
+        return result;
+    }
+
     @Transactional(readOnly = true)
     public List<ConnectorObject> match(
             final Connector connector,
@@ -169,19 +197,11 @@
             final Optional<String[]> moreAttrsToGet,
             final Item... linkingItems) {
 
-        Set<String> matgFromPropagationActions = new HashSet<>();
-        resource.getPropagationActions().forEach(impl -> {
-            try {
-                matgFromPropagationActions.addAll(
-                        ImplementationManager.<PropagationActions>build(impl).
-                                moreAttrsToGet(Optional.empty(), provision));
-            } catch (Exception e) {
-                LOG.error("While building {}", impl, e);
-            }
-        });
+        Stream<String> matgFromPropagationActions = getPropagationActions(resource).stream().
+                flatMap(a -> a.moreAttrsToGet(Optional.empty(), provision).stream());
         Optional<String[]> effectiveMATG = Optional.of(Stream.concat(
                 moreAttrsToGet.stream().flatMap(Stream::of),
-                matgFromPropagationActions.stream()).toArray(String[]::new));
+                matgFromPropagationActions).toArray(String[]::new));
 
         Optional<PushCorrelationRule> rule = rule(resource, provision);
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
index 8dd2760..f077b2c 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PullJobDelegate.java
@@ -25,6 +25,7 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.commons.lang3.StringUtils;
@@ -34,6 +35,7 @@
 import org.apache.syncope.common.lib.to.Provision;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
 import org.apache.syncope.common.lib.types.ResourceOperation;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 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.PlainSchemaDAO;
@@ -42,6 +44,7 @@
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.task.PullTask;
@@ -87,11 +90,18 @@
     @Autowired
     protected AnyUtilsFactory anyUtilsFactory;
 
+    @Autowired
+    protected PlainAttrValidationManager validator;
+
     protected final Map<String, SyncToken> latestSyncTokens = new HashMap<>();
 
+    protected ProvisioningProfile<PullTask, PullActions> profile;
+
     protected final Map<String, MutablePair<Integer, String>> handled = new HashMap<>();
 
-    protected ProvisioningProfile<PullTask, PullActions> profile;
+    protected final Map<String, PullActions> perContextActions = new ConcurrentHashMap<>();
+
+    protected Optional<ReconFilterBuilder> perContextReconFilterBuilder = Optional.empty();
 
     @Override
     public void setLatestSyncToken(final String objectClass, final SyncToken latestSyncToken) {
@@ -165,22 +175,28 @@
         });
     }
 
-    protected List<PullActions> buildPullActions(final PullTask pullTask) {
-        List<PullActions> actions = new ArrayList<>();
-        pullTask.getActions().forEach(impl -> {
+    protected List<PullActions> getPullActions(final List<? extends Implementation> impls) {
+        List<PullActions> result = new ArrayList<>();
+
+        impls.forEach(impl -> {
             try {
-                actions.add(ImplementationManager.build(impl));
+                result.add(ImplementationManager.build(
+                        impl,
+                        () -> perContextActions.get(impl.getKey()),
+                        instance -> perContextActions.put(impl.getKey(), instance)));
             } catch (Exception e) {
                 LOG.warn("While building {}", impl, e);
             }
         });
-        return actions;
+
+        return result;
     }
 
-    protected ReconFilterBuilder buildReconFilterBuilder(final PullTask pullTask)
-            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
-
-        return ImplementationManager.build(pullTask.getReconFilterBuilder());
+    protected ReconFilterBuilder getReconFilterBuilder(final PullTask pullTask) throws ClassNotFoundException {
+        return ImplementationManager.build(
+                pullTask.getReconFilterBuilder(),
+                () -> perContextReconFilterBuilder.orElse(null),
+                instance -> perContextReconFilterBuilder = Optional.of(instance));
     }
 
     protected RealmPullResultHandler buildRealmHandler() {
@@ -217,10 +233,8 @@
 
         LOG.debug("Executing pull on {}", pullTask.getResource());
 
-        List<PullActions> actions = buildPullActions(pullTask);
-
         profile = new ProvisioningProfile<>(connector, pullTask);
-        profile.getActions().addAll(actions);
+        profile.getActions().addAll(getPullActions(pullTask.getActions()));
         profile.setDryRun(dryRun);
         profile.setConflictResolutionAction(pullTask.getResource().getPullPolicy() == null
                 ? ConflictResolutionAction.IGNORE
@@ -230,7 +244,7 @@
         latestSyncTokens.clear();
 
         if (!profile.isDryRun()) {
-            for (PullActions action : actions) {
+            for (PullActions action : profile.getActions()) {
                 action.beforeAll(profile);
             }
         }
@@ -244,7 +258,7 @@
             OrgUnit orgUnit = pullTask.getResource().getOrgUnit();
 
             Set<String> moreAttrsToGet = new HashSet<>();
-            actions.forEach(action -> moreAttrsToGet.addAll(action.moreAttrsToGet(profile, orgUnit)));
+            profile.getActions().forEach(a -> moreAttrsToGet.addAll(a.moreAttrsToGet(profile, orgUnit)));
             OperationOptions options = MappingUtils.buildOperationOptions(
                     MappingUtils.getPullItems(orgUnit.getItems().stream()), moreAttrsToGet.toArray(String[]::new));
 
@@ -274,9 +288,8 @@
                         break;
 
                     case FILTERED_RECONCILIATION:
-                        connector.filteredReconciliation(
-                                new ObjectClass(orgUnit.getObjectClass()),
-                                buildReconFilterBuilder(pullTask),
+                        connector.filteredReconciliation(new ObjectClass(orgUnit.getObjectClass()),
+                                getReconFilterBuilder(pullTask),
                                 handler,
                                 options);
                         break;
@@ -295,14 +308,7 @@
         }
 
         // ...then provisions for any types
-        ProvisionSorter provisionSorter = new DefaultProvisionSorter();
-        if (pullTask.getResource().getProvisionSorter() != null) {
-            try {
-                provisionSorter = ImplementationManager.build(pullTask.getResource().getProvisionSorter());
-            } catch (Exception e) {
-                LOG.error("While building {}", pullTask.getResource().getProvisionSorter(), e);
-            }
-        }
+        ProvisionSorter provisionSorter = getProvisionSorter(pullTask);
 
         GroupPullResultHandler ghandler = buildGroupHandler();
         for (Provision provision : pullTask.getResource().getProvisions().stream().
@@ -332,7 +338,7 @@
 
             try {
                 Set<String> moreAttrsToGet = new HashSet<>();
-                actions.forEach(action -> moreAttrsToGet.addAll(action.moreAttrsToGet(profile, provision)));
+                profile.getActions().forEach(a -> moreAttrsToGet.addAll(a.moreAttrsToGet(profile, provision)));
                 Stream<Item> mapItems = Stream.concat(
                         MappingUtils.getPullItems(provision.getMapping().getItems().stream()),
                         virSchemaDAO.find(pullTask.getResource().getKey(), anyType.getKey()).stream().
@@ -362,9 +368,8 @@
                         break;
 
                     case FILTERED_RECONCILIATION:
-                        connector.filteredReconciliation(
-                                new ObjectClass(provision.getObjectClass()),
-                                buildReconFilterBuilder(pullTask),
+                        connector.filteredReconciliation(new ObjectClass(provision.getObjectClass()),
+                                getReconFilterBuilder(pullTask),
                                 handler,
                                 options);
                         break;
@@ -385,7 +390,9 @@
                             && result.getOperation() == ResourceOperation.CREATE
                             && result.getAnyType().equals(provision.getAnyType())).
                             forEach(result -> anyUtils.addAttr(
-                            result.getKey(), plainSchemaDAO.find(provision.getUidOnCreate()), result.getUidValue()));
+                            validator,
+                            result.getKey(),
+                            plainSchemaDAO.find(provision.getUidOnCreate()), result.getUidValue()));
                 }
             } catch (Throwable t) {
                 throw new JobExecutionException("While pulling from connector", t);
@@ -398,7 +405,7 @@
         }
 
         if (!profile.isDryRun()) {
-            for (PullActions action : actions) {
+            for (PullActions action : profile.getActions()) {
                 action.afterAll(profile);
             }
         }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
index b6dcf69..18305af 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
@@ -24,6 +24,7 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.MutablePair;
@@ -38,6 +39,7 @@
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
@@ -83,6 +85,8 @@
 
     protected final Map<String, MutablePair<Integer, String>> handled = new HashMap<>();
 
+    protected final Map<String, PushActions> perContextActions = new ConcurrentHashMap<>();
+
     protected void reportHandled(final String anyType, final String key) {
         MutablePair<Integer, String> pair = handled.get(anyType);
         if (pair == null) {
@@ -151,6 +155,23 @@
                 createBean(DefaultGroupPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
     }
 
+    protected List<PushActions> getPushActions(final List<? extends Implementation> impls) {
+        List<PushActions> result = new ArrayList<>();
+
+        impls.forEach(impl -> {
+            try {
+                result.add(ImplementationManager.build(
+                        impl,
+                        () -> perContextActions.get(impl.getKey()),
+                        instance -> perContextActions.put(impl.getKey(), instance)));
+            } catch (Exception e) {
+                LOG.warn("While building {}", impl, e);
+            }
+        });
+
+        return result;
+    }
+
     @Override
     protected String doExecuteProvisioning(
             final PushTask pushTask,
@@ -161,17 +182,8 @@
 
         LOG.debug("Executing push on {}", pushTask.getResource());
 
-        List<PushActions> actions = new ArrayList<>();
-        pushTask.getActions().forEach(impl -> {
-            try {
-                actions.add(ImplementationManager.build(impl));
-            } catch (Exception e) {
-                LOG.warn("While building {}", impl, e);
-            }
-        });
-
         profile = new ProvisioningProfile<>(connector, pushTask);
-        profile.getActions().addAll(actions);
+        profile.getActions().addAll(getPushActions(pushTask.getActions()));
         profile.setDryRun(dryRun);
         profile.setConflictResolutionAction(pushTask.getResource().getPushPolicy() == null
                 ? ConflictResolutionAction.IGNORE
@@ -179,7 +191,7 @@
         profile.setExecutor(executor);
 
         if (!profile.isDryRun()) {
-            for (PushActions action : actions) {
+            for (PushActions action : profile.getActions()) {
                 action.beforeAll(profile);
             }
         }
@@ -208,14 +220,7 @@
         }
 
         // ...then provisions for any types
-        ProvisionSorter provisionSorter = new DefaultProvisionSorter();
-        if (pushTask.getResource().getProvisionSorter() != null) {
-            try {
-                provisionSorter = ImplementationManager.build(pushTask.getResource().getProvisionSorter());
-            } catch (Exception e) {
-                LOG.error("While building {}", pushTask.getResource().getProvisionSorter(), e);
-            }
-        }
+        ProvisionSorter provisionSorter = getProvisionSorter(pushTask);
 
         for (Provision provision : pushTask.getResource().getProvisions().stream().
                 filter(provision -> provision.getMapping() != null).sorted(provisionSorter).
@@ -269,7 +274,7 @@
         }
 
         if (!profile.isDryRun() && !interrupt) {
-            for (PushActions action : actions) {
+            for (PushActions action : profile.getActions()) {
                 action.afterAll(profile);
             }
         }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
index 390dcbc..a71163e 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
@@ -18,10 +18,11 @@
  */
 package org.apache.syncope.core.provisioning.java.pushpull;
 
-import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.syncope.common.lib.to.Item;
 import org.apache.syncope.common.lib.to.Provision;
@@ -29,7 +30,6 @@
 import org.apache.syncope.common.lib.to.PullTaskTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
-import org.apache.syncope.common.lib.types.IdMImplementationType;
 import org.apache.syncope.common.lib.types.MatchingRule;
 import org.apache.syncope.common.lib.types.PullMode;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
@@ -37,7 +37,6 @@
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
-import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.task.AnyTemplatePullTask;
 import org.apache.syncope.core.persistence.api.entity.task.PullTask;
@@ -50,7 +49,6 @@
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopeSinglePullExecutor;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
 import org.apache.syncope.core.provisioning.java.utils.TemplateUtils;
-import org.apache.syncope.core.spring.ImplementationManager;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.quartz.JobExecutionException;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -75,20 +73,6 @@
 
         LOG.debug("Executing pull on {}", resource);
 
-        List<PullActions> actions = new ArrayList<>();
-        pullTaskTO.getActions().forEach(key -> {
-            Implementation impl = implementationDAO.find(key);
-            if (impl == null || !IdMImplementationType.PULL_ACTIONS.equals(impl.getType())) {
-                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...", key);
-            } else {
-                try {
-                    actions.add(ImplementationManager.build(impl));
-                } catch (Exception e) {
-                    LOG.warn("While building {}", impl, e);
-                }
-            }
-        });
-
         try {
             PullTask pullTask = entityFactory.newEntity(PullTask.class);
             pullTask.setResource(resource);
@@ -125,10 +109,11 @@
             profile = new ProvisioningProfile<>(connector, pullTask);
             profile.setDryRun(false);
             profile.setConflictResolutionAction(ConflictResolutionAction.FIRSTMATCH);
-            profile.getActions().addAll(actions);
+            profile.getActions().addAll(getPullActions(pullTaskTO.getActions().stream().
+                    map(implementationDAO::find).filter(Objects::nonNull).collect(Collectors.toList())));
             profile.setExecutor(executor);
 
-            for (PullActions action : actions) {
+            for (PullActions action : profile.getActions()) {
                 action.beforeAll(profile);
             }
 
@@ -154,7 +139,7 @@
 
             // execute filtered pull
             Set<String> matg = new HashSet<>(moreAttrsToGet);
-            actions.forEach(action -> matg.addAll(action.moreAttrsToGet(profile, provision)));
+            profile.getActions().forEach(a -> matg.addAll(a.moreAttrsToGet(profile, provision)));
 
             Stream<Item> mapItems = Stream.concat(
                     MappingUtils.getPullItems(provision.getMapping().getItems().stream()),
@@ -173,7 +158,7 @@
                 LOG.error("While setting group owners", e);
             }
 
-            for (PullActions action : actions) {
+            for (PullActions action : profile.getActions()) {
                 action.afterAll(profile);
             }
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java
index 6c8685e..9e67fce 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java
@@ -18,20 +18,19 @@
  */
 package org.apache.syncope.core.provisioning.java.pushpull;
 
-import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.to.Provision;
 import org.apache.syncope.common.lib.to.ProvisioningReport;
 import org.apache.syncope.common.lib.to.PushTaskTO;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
-import org.apache.syncope.common.lib.types.IdMImplementationType;
 import org.apache.syncope.common.lib.types.MatchingRule;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
 import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
-import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.task.PushTask;
 import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
 import org.apache.syncope.core.provisioning.api.Connector;
@@ -40,7 +39,6 @@
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopePushResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.SyncopeSinglePushExecutor;
 import org.apache.syncope.core.provisioning.api.pushpull.UserPushResultHandler;
-import org.apache.syncope.core.spring.ImplementationManager;
 import org.quartz.JobExecutionException;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -49,7 +47,7 @@
     @Autowired
     protected ImplementationDAO implementationDAO;
 
-    protected List<PushActions> before(
+    protected void before(
             final ExternalResource resource,
             final Connector connector,
             final PushTaskTO pushTaskTO,
@@ -57,20 +55,6 @@
 
         LOG.debug("Executing push on {}", resource);
 
-        List<PushActions> actions = new ArrayList<>();
-        pushTaskTO.getActions().forEach(key -> {
-            Implementation impl = implementationDAO.find(key);
-            if (impl == null || !IdMImplementationType.PUSH_ACTIONS.equals(impl.getType())) {
-                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...", key);
-            } else {
-                try {
-                    actions.add(ImplementationManager.build(impl));
-                } catch (Exception e) {
-                    LOG.warn("While building {}", impl, e);
-                }
-            }
-        });
-
         PushTask pushTask = entityFactory.newEntity(PushTask.class);
         pushTask.setResource(resource);
         pushTask.setMatchingRule(pushTaskTO.getMatchingRule() == null
@@ -84,14 +68,13 @@
 
         profile = new ProvisioningProfile<>(connector, pushTask);
         profile.setExecutor(executor);
-        profile.getActions().addAll(actions);
+        profile.getActions().addAll(getPushActions(pushTaskTO.getActions().stream().
+                map(implementationDAO::find).filter(Objects::nonNull).collect(Collectors.toList())));
         profile.setConflictResolutionAction(ConflictResolutionAction.FIRSTMATCH);
 
-        for (PushActions action : actions) {
+        for (PushActions action : profile.getActions()) {
             action.beforeAll(profile);
         }
-
-        return actions;
     }
 
     @Override
@@ -104,7 +87,7 @@
             final String executor) throws JobExecutionException {
 
         try {
-            List<PushActions> actions = before(resource, connector, pushTaskTO, executor);
+            before(resource, connector, pushTaskTO, executor);
 
             AnyType anyType = anyTypeDAO.find(provision.getAnyType());
 
@@ -126,7 +109,7 @@
 
             doHandle(List.of(any), handler, resource);
 
-            for (PushActions action : actions) {
+            for (PushActions action : profile.getActions()) {
                 action.afterAll(profile);
             }
 
@@ -148,14 +131,14 @@
             final String executor) throws JobExecutionException {
 
         try {
-            List<PushActions> actions = before(resource, connector, pushTaskTO, executor);
+            before(resource, connector, pushTaskTO, executor);
 
             UserPushResultHandler handler = buildUserHandler();
             handler.setProfile(profile);
 
             handler.handle(account, provision);
 
-            for (PushActions action : actions) {
+            for (PushActions action : profile.getActions()) {
                 action.afterAll(profile);
             }
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
index b7cb57a..4d39209 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
@@ -18,10 +18,11 @@
  */
 package org.apache.syncope.core.provisioning.java.pushpull.stream;
 
-import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.syncope.common.lib.to.Item;
 import org.apache.syncope.common.lib.to.Mapping;
@@ -52,7 +53,6 @@
 import org.apache.syncope.core.provisioning.api.pushpull.stream.SyncopeStreamPullExecutor;
 import org.apache.syncope.core.provisioning.java.pushpull.PullJobDelegate;
 import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
-import org.apache.syncope.core.spring.ImplementationManager;
 import org.apache.syncope.core.spring.security.SecureRandomUtils;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.quartz.JobExecutionException;
@@ -168,20 +168,6 @@
 
         LOG.debug("Executing stream pull");
 
-        List<PullActions> actions = new ArrayList<>();
-        pullTaskTO.getActions().forEach(key -> {
-            Implementation impl = implementationDAO.find(key);
-            if (impl == null || !IdMImplementationType.PULL_ACTIONS.equals(impl.getType())) {
-                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...", key);
-            } else {
-                try {
-                    actions.add(ImplementationManager.build(impl));
-                } catch (Exception e) {
-                    LOG.warn("While building {}", impl, e);
-                }
-            }
-        });
-
         try {
             ExternalResource resource =
                     externalResource(anyType, keyColumn, columns, conflictResolutionAction, pullCorrelationRule);
@@ -202,9 +188,10 @@
             profile = new ProvisioningProfile<>(connector, pullTask);
             profile.setDryRun(false);
             profile.setConflictResolutionAction(conflictResolutionAction);
-            profile.getActions().addAll(actions);
+            profile.getActions().addAll(getPullActions(pullTaskTO.getActions().stream().
+                    map(implementationDAO::find).filter(Objects::nonNull).collect(Collectors.toList())));
 
-            for (PullActions action : actions) {
+            for (PullActions action : profile.getActions()) {
                 action.beforeAll(profile);
             }
 
@@ -228,7 +215,7 @@
 
             // execute filtered pull
             Set<String> moreAttrsToGet = new HashSet<>();
-            actions.forEach(action -> moreAttrsToGet.addAll(action.moreAttrsToGet(profile, provision)));
+            profile.getActions().forEach(a -> moreAttrsToGet.addAll(a.moreAttrsToGet(profile, provision)));
 
             Stream<Item> mapItems = Stream.concat(
                     MappingUtils.getPullItems(provision.getMapping().getItems().stream()),
@@ -246,7 +233,7 @@
                 LOG.error("While setting group owners", e);
             }
 
-            for (PullActions action : actions) {
+            for (PullActions action : profile.getActions()) {
                 action.afterAll(profile);
             }
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java
index 14bf801..7c9c765 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java
@@ -18,8 +18,9 @@
  */
 package org.apache.syncope.core.provisioning.java.pushpull.stream;
 
-import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.to.Item;
 import org.apache.syncope.common.lib.to.Mapping;
 import org.apache.syncope.common.lib.to.Provision;
@@ -44,7 +45,6 @@
 import org.apache.syncope.core.provisioning.api.pushpull.stream.SyncopeStreamPushExecutor;
 import org.apache.syncope.core.provisioning.java.pushpull.PushJobDelegate;
 import org.apache.syncope.core.spring.ApplicationContextProvider;
-import org.apache.syncope.core.spring.ImplementationManager;
 import org.apache.syncope.core.spring.security.SecureRandomUtils;
 import org.quartz.JobExecutionException;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -128,20 +128,6 @@
 
         LOG.debug("Executing stream push as {}", executor);
 
-        List<PushActions> pushActions = new ArrayList<>();
-        pushTaskTO.getActions().forEach(key -> {
-            Implementation impl = implementationDAO.find(key);
-            if (impl == null || !IdMImplementationType.PUSH_ACTIONS.equals(impl.getType())) {
-                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...", key);
-            } else {
-                try {
-                    pushActions.add(ImplementationManager.build(impl));
-                } catch (Exception e) {
-                    LOG.warn("While building {}", impl, e);
-                }
-            }
-        });
-
         try {
             ExternalResource resource = externalResource(anyType, columns, propagationActions);
 
@@ -156,10 +142,11 @@
 
             profile = new ProvisioningProfile<>(connector, pushTask);
             profile.setExecutor(executor);
-            profile.getActions().addAll(pushActions);
+            profile.getActions().addAll(getPushActions(pushTaskTO.getActions().stream().
+                    map(implementationDAO::find).filter(Objects::nonNull).collect(Collectors.toList())));
             profile.setConflictResolutionAction(ConflictResolutionAction.FIRSTMATCH);
 
-            for (PushActions action : pushActions) {
+            for (PushActions action : profile.getActions()) {
                 action.beforeAll(profile);
             }
 
@@ -181,7 +168,7 @@
 
             doHandle(anys, handler, resource);
 
-            for (PushActions action : pushActions) {
+            for (PushActions action : profile.getActions()) {
                 action.afterAll(profile);
             }
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
index bf48dc0..46d49cb 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/MappingUtils.java
@@ -21,8 +21,10 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Stream;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -49,6 +51,8 @@
 
     private static final Logger LOG = LoggerFactory.getLogger(MappingUtils.class);
 
+    private static final Map<String, ItemTransformer> PER_CONTEXT_ITEM_TRANSFORMERS = new ConcurrentHashMap<>();
+
     public static Optional<Item> getConnObjectKeyItem(final Provision provision) {
         Mapping mapping = null;
         if (provision != null) {
@@ -91,7 +95,10 @@
         // Then other custom transformers
         transformers.forEach(impl -> {
             try {
-                result.add(ImplementationManager.build(impl));
+                result.add(ImplementationManager.build(
+                        impl,
+                        () -> PER_CONTEXT_ITEM_TRANSFORMERS.get(impl.getKey()),
+                        instance -> PER_CONTEXT_ITEM_TRANSFORMERS.put(impl.getKey(), instance)));
             } catch (Exception e) {
                 LOG.error("While building {}", impl, e);
             }
diff --git a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DefaultMappingManagerTest.java b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DefaultMappingManagerTest.java
index 86f038a..e15ed0c 100644
--- a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DefaultMappingManagerTest.java
+++ b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/DefaultMappingManagerTest.java
@@ -30,6 +30,7 @@
 import org.apache.syncope.common.lib.to.Provision;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.CipherAlgorithm;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -84,6 +85,9 @@
     @Autowired
     private EntityFactory entityFactory;
 
+    @Autowired
+    private PlainAttrValidationManager validator;
+
     @Test
     public void prepareAttrsForUser() {
         User bellini = userDAO.findByUsername("bellini");
@@ -251,7 +255,7 @@
         UPlainAttr cool = entityFactory.newEntity(UPlainAttr.class);
         cool.setOwner(user);
         cool.setSchema(plainSchemaDAO.find("cool"));
-        cool.add("true", anyUtilsFactory.getInstance(AnyTypeKind.USER));
+        cool.add(validator, "true", anyUtilsFactory.getInstance(AnyTypeKind.USER));
         user.add(cool);
 
         user = userDAO.save(user);
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/ImplementationManager.java b/core/spring/src/main/java/org/apache/syncope/core/spring/ImplementationManager.java
deleted file mode 100644
index 7b69fee..0000000
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/ImplementationManager.java
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.syncope.core.spring;
-
-import groovy.lang.GroovyClassLoader;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import org.apache.syncope.common.lib.policy.AccountRuleConf;
-import org.apache.syncope.common.lib.policy.PasswordRuleConf;
-import org.apache.syncope.common.lib.policy.PullCorrelationRuleConf;
-import org.apache.syncope.common.lib.policy.PushCorrelationRuleConf;
-import org.apache.syncope.common.lib.report.ReportletConf;
-import org.apache.syncope.core.persistence.api.ImplementationLookup;
-import org.apache.syncope.core.persistence.api.dao.AccountRule;
-import org.apache.syncope.core.persistence.api.dao.PasswordRule;
-import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule;
-import org.apache.syncope.core.persistence.api.dao.PushCorrelationRule;
-import org.apache.syncope.core.persistence.api.dao.Reportlet;
-import org.apache.syncope.core.persistence.api.entity.Implementation;
-import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
-import org.apache.syncope.core.spring.security.AuthContextUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.support.AbstractBeanDefinition;
-import org.springframework.beans.factory.support.DefaultListableBeanFactory;
-
-public final class ImplementationManager {
-
-    private static final Logger LOG = LoggerFactory.getLogger(ImplementationManager.class);
-
-    private static final GroovyClassLoader GROOVY_CLASSLOADER = new GroovyClassLoader();
-
-    private static final Map<String, Class<?>> CLASS_CACHE = Collections.synchronizedMap(new HashMap<>());
-
-    public static Optional<Reportlet> buildReportlet(final Implementation impl)
-            throws InstantiationException, IllegalAccessException {
-
-        switch (impl.getEngine()) {
-            case GROOVY:
-                return Optional.of(ImplementationManager.<Reportlet>buildGroovy(impl));
-
-            case JAVA:
-            default:
-                ReportletConf reportletConf = POJOHelper.deserialize(impl.getBody(), ReportletConf.class);
-                Class<? extends Reportlet> reportletClass = ApplicationContextProvider.getApplicationContext().
-                        getBean(ImplementationLookup.class).getReportletClass(reportletConf.getClass());
-
-                Reportlet reportlet = buildJavaWithConf(reportletClass);
-                if (reportlet == null) {
-                    LOG.warn("Could not find matching reportlet for {}", reportletConf.getClass());
-                } else {
-                    reportlet.setConf(reportletConf);
-                }
-
-                return Optional.ofNullable(reportlet);
-        }
-    }
-
-    public static Optional<AccountRule> buildAccountRule(final Implementation impl)
-            throws InstantiationException, IllegalAccessException {
-
-        switch (impl.getEngine()) {
-            case GROOVY:
-                return Optional.of(ImplementationManager.<AccountRule>buildGroovy(impl));
-
-            case JAVA:
-            default:
-                AccountRuleConf ruleConf = POJOHelper.deserialize(impl.getBody(), AccountRuleConf.class);
-                Class<? extends AccountRule> ruleClass = ApplicationContextProvider.getApplicationContext().
-                        getBean(ImplementationLookup.class).getAccountRuleClass(ruleConf.getClass());
-
-                AccountRule rule = buildJavaWithConf(ruleClass);
-                if (rule == null) {
-                    LOG.warn("Could not find matching account rule for {}", impl.getClass());
-                } else {
-                    rule.setConf(ruleConf);
-                }
-
-                return Optional.ofNullable(rule);
-        }
-    }
-
-    public static Optional<PasswordRule> buildPasswordRule(final Implementation impl)
-            throws InstantiationException, IllegalAccessException {
-
-        switch (impl.getEngine()) {
-            case GROOVY:
-                return Optional.of(ImplementationManager.<PasswordRule>buildGroovy(impl));
-
-            case JAVA:
-            default:
-                PasswordRuleConf ruleConf = POJOHelper.deserialize(impl.getBody(), PasswordRuleConf.class);
-                Class<? extends PasswordRule> ruleClass = ApplicationContextProvider.getApplicationContext().
-                        getBean(ImplementationLookup.class).getPasswordRuleClass(ruleConf.getClass());
-
-                PasswordRule rule = buildJavaWithConf(ruleClass);
-                if (rule == null) {
-                    LOG.warn("Could not find matching password rule for {}", impl.getClass());
-                } else {
-                    rule.setConf(ruleConf);
-                }
-
-                return Optional.ofNullable(rule);
-        }
-    }
-
-    public static Optional<PullCorrelationRule> buildPullCorrelationRule(final Implementation impl)
-            throws InstantiationException, IllegalAccessException {
-
-        switch (impl.getEngine()) {
-            case GROOVY:
-                return Optional.of(ImplementationManager.<PullCorrelationRule>buildGroovy(impl));
-
-            case JAVA:
-            default:
-                PullCorrelationRuleConf ruleConf =
-                        POJOHelper.deserialize(impl.getBody(), PullCorrelationRuleConf.class);
-                Class<? extends PullCorrelationRule> ruleClass = ApplicationContextProvider.getApplicationContext().
-                        getBean(ImplementationLookup.class).getPullCorrelationRuleClass(ruleConf.getClass());
-
-                PullCorrelationRule rule = buildJavaWithConf(ruleClass);
-                if (rule == null) {
-                    LOG.warn("Could not find matching pull correlation rule for {}", impl.getClass());
-                } else {
-                    rule.setConf(ruleConf);
-                }
-
-                return Optional.ofNullable(rule);
-        }
-    }
-
-    public static Optional<PushCorrelationRule> buildPushCorrelationRule(final Implementation impl)
-            throws InstantiationException, IllegalAccessException {
-
-        switch (impl.getEngine()) {
-            case GROOVY:
-                return Optional.of(ImplementationManager.<PushCorrelationRule>buildGroovy(impl));
-
-            case JAVA:
-            default:
-                PushCorrelationRuleConf ruleConf =
-                        POJOHelper.deserialize(impl.getBody(), PushCorrelationRuleConf.class);
-                Class<? extends PushCorrelationRule> ruleClass = ApplicationContextProvider.getApplicationContext().
-                        getBean(ImplementationLookup.class).getPushCorrelationRuleClass(ruleConf.getClass());
-
-                PushCorrelationRule rule = buildJavaWithConf(ruleClass);
-                if (rule == null) {
-                    LOG.warn("Could not find matching push correlation rule for {}", impl.getClass());
-                } else {
-                    rule.setConf(ruleConf);
-                }
-
-                return Optional.ofNullable(rule);
-        }
-    }
-
-    public static <T> T build(final Implementation impl)
-            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
-
-        switch (impl.getEngine()) {
-            case GROOVY:
-                return ImplementationManager.<T>buildGroovy(impl);
-
-            case JAVA:
-            default:
-                return ImplementationManager.<T>buildJava(impl);
-        }
-    }
-
-    @SuppressWarnings("unchecked")
-    private static <T> T buildGroovy(final Implementation impl)
-            throws InstantiationException, IllegalAccessException {
-
-        Class<?> clazz;
-        if (CLASS_CACHE.containsKey(impl.getKey())) {
-            clazz = CLASS_CACHE.get(impl.getKey());
-        } else {
-            clazz = GROOVY_CLASSLOADER.parseClass(impl.getBody());
-            CLASS_CACHE.put(impl.getKey(), clazz);
-        }
-
-        return (T) ApplicationContextProvider.getBeanFactory().
-                createBean(clazz, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
-    }
-
-    @SuppressWarnings("unchecked")
-    private static <T> T buildJava(final Implementation impl)
-            throws ClassNotFoundException {
-
-        Class<?> clazz;
-        if (CLASS_CACHE.containsKey(impl.getKey())) {
-            clazz = CLASS_CACHE.get(impl.getKey());
-        } else {
-            clazz = Class.forName(impl.getBody());
-            CLASS_CACHE.put(impl.getKey(), clazz);
-        }
-
-        return (T) ApplicationContextProvider.getBeanFactory().
-                createBean(clazz, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
-    }
-
-    @SuppressWarnings("unchecked")
-    private static <T> T buildJavaWithConf(final Class<T> clazz) {
-        if (clazz != null) {
-            String domainableBeanNameWithConf = AuthContextUtils.getDomain() + clazz.getName();
-            DefaultListableBeanFactory beanFactory = ApplicationContextProvider.getBeanFactory();
-
-            if (beanFactory.containsSingleton(domainableBeanNameWithConf)) {
-                return (T) beanFactory.getSingleton(domainableBeanNameWithConf);
-            }
-
-            synchronized (beanFactory.getSingletonMutex()) {
-                if (beanFactory.containsSingleton(domainableBeanNameWithConf)) {
-                    return (T) beanFactory.getSingleton(domainableBeanNameWithConf);
-                } else {
-                    T bean = (T) beanFactory.createBean(clazz, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
-                    beanFactory.registerSingleton(domainableBeanNameWithConf, bean);
-                    return bean;
-                }
-            }
-        }
-        return null;
-    }
-
-    public static Class<?> purge(final String implementation) {
-        return CLASS_CACHE.remove(implementation);
-    }
-
-    private ImplementationManager() {
-        // private constructor for static utility class
-    }
-}
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/ImplementationManager.java b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/ImplementationManager.java
new file mode 100644
index 0000000..9ca5410
--- /dev/null
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/ImplementationManager.java
@@ -0,0 +1,252 @@
+/*
+ * 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.spring;
+
+import groovy.lang.GroovyClassLoader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.common.lib.policy.AccountRuleConf;
+import org.apache.syncope.common.lib.policy.PasswordRuleConf;
+import org.apache.syncope.common.lib.policy.PullCorrelationRuleConf;
+import org.apache.syncope.common.lib.policy.PushCorrelationRuleConf;
+import org.apache.syncope.common.lib.report.ReportletConf;
+import org.apache.syncope.core.persistence.api.ImplementationLookup;
+import org.apache.syncope.core.persistence.api.dao.AccountRule;
+import org.apache.syncope.core.persistence.api.dao.PasswordRule;
+import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule;
+import org.apache.syncope.core.persistence.api.dao.PushCorrelationRule;
+import org.apache.syncope.core.persistence.api.dao.Reportlet;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+import org.apache.syncope.core.spring.implementation.InstanceScope;
+import org.apache.syncope.core.spring.implementation.SyncopeImplementation;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+
+public final class ImplementationManager {
+
+    private static final GroovyClassLoader GROOVY_CLASSLOADER = new GroovyClassLoader();
+
+    private static final Map<String, Class<?>> CLASS_CACHE = Collections.synchronizedMap(new HashMap<>());
+
+    public static Optional<Reportlet> buildReportlet(final Implementation impl) throws ClassNotFoundException {
+        switch (impl.getEngine()) {
+            case GROOVY:
+                return Optional.of(build(impl));
+
+            case JAVA:
+            default:
+                ReportletConf conf = POJOHelper.deserialize(impl.getBody(), ReportletConf.class);
+                Class<? extends Reportlet> clazz = ApplicationContextProvider.getApplicationContext().
+                        getBean(ImplementationLookup.class).getReportletClass(conf.getClass());
+
+                if (clazz == null) {
+                    return Optional.empty();
+                }
+
+                Reportlet reportlet = (Reportlet) ApplicationContextProvider.getBeanFactory().
+                        createBean(clazz, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+                reportlet.setConf(conf);
+                return Optional.of(reportlet);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Optional<AccountRule> buildAccountRule(
+            final Implementation impl,
+            final Supplier<AccountRule> cacheGetter,
+            final Consumer<AccountRule> cachePutter)
+            throws ClassNotFoundException {
+
+        switch (impl.getEngine()) {
+            case GROOVY:
+                return Optional.of(build(impl, cacheGetter, cachePutter));
+
+            case JAVA:
+            default:
+                AccountRuleConf conf = POJOHelper.deserialize(impl.getBody(), AccountRuleConf.class);
+                Class<AccountRule> clazz = (Class<AccountRule>) ApplicationContextProvider.getApplicationContext().
+                        getBean(ImplementationLookup.class).getAccountRuleClass(conf.getClass());
+
+                if (clazz == null) {
+                    return Optional.empty();
+                }
+
+                AccountRule rule = build(clazz, true, cacheGetter, cachePutter);
+                rule.setConf(conf);
+                return Optional.of(rule);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Optional<PasswordRule> buildPasswordRule(
+            final Implementation impl,
+            final Supplier<PasswordRule> cacheGetter,
+            final Consumer<PasswordRule> cachePutter)
+            throws ClassNotFoundException {
+
+        switch (impl.getEngine()) {
+            case GROOVY:
+                return Optional.of(build(impl, cacheGetter, cachePutter));
+
+            case JAVA:
+            default:
+                PasswordRuleConf conf = POJOHelper.deserialize(impl.getBody(), PasswordRuleConf.class);
+                Class<PasswordRule> clazz = (Class<PasswordRule>) ApplicationContextProvider.getApplicationContext().
+                        getBean(ImplementationLookup.class).getPasswordRuleClass(conf.getClass());
+
+                if (clazz == null) {
+                    return Optional.empty();
+                }
+
+                PasswordRule rule = build(clazz, true, cacheGetter, cachePutter);
+                rule.setConf(conf);
+                return Optional.of(rule);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Optional<PullCorrelationRule> buildPullCorrelationRule(
+            final Implementation impl,
+            final Supplier<PullCorrelationRule> cacheGetter,
+            final Consumer<PullCorrelationRule> cachePutter)
+            throws ClassNotFoundException {
+
+        switch (impl.getEngine()) {
+            case GROOVY:
+                return Optional.of(build(impl, cacheGetter, cachePutter));
+
+            case JAVA:
+            default:
+                PullCorrelationRuleConf conf = POJOHelper.deserialize(impl.getBody(), PullCorrelationRuleConf.class);
+                Class<PullCorrelationRule> clazz =
+                        (Class<PullCorrelationRule>) ApplicationContextProvider.getApplicationContext().
+                                getBean(ImplementationLookup.class).getPullCorrelationRuleClass(conf.getClass());
+
+                if (clazz == null) {
+                    return Optional.empty();
+                }
+
+                PullCorrelationRule rule = build(clazz, true, cacheGetter, cachePutter);
+                rule.setConf(conf);
+                return Optional.of(rule);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public static Optional<PushCorrelationRule> buildPushCorrelationRule(
+            final Implementation impl,
+            final Supplier<PushCorrelationRule> cacheGetter,
+            final Consumer<PushCorrelationRule> cachePutter)
+            throws ClassNotFoundException {
+
+        switch (impl.getEngine()) {
+            case GROOVY:
+                return Optional.of(build(impl, cacheGetter, cachePutter));
+
+            case JAVA:
+            default:
+                PushCorrelationRuleConf conf = POJOHelper.deserialize(impl.getBody(), PushCorrelationRuleConf.class);
+                Class<PushCorrelationRule> clazz =
+                        (Class<PushCorrelationRule>) ApplicationContextProvider.getApplicationContext().
+                                getBean(ImplementationLookup.class).getPushCorrelationRuleClass(conf.getClass());
+
+                if (clazz == null) {
+                    return Optional.empty();
+                }
+
+                PushCorrelationRule rule = build(clazz, true, cacheGetter, cachePutter);
+                rule.setConf(conf);
+                return Optional.of(rule);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> Pair<Class<T>, Boolean> getClass(final Implementation impl) throws ClassNotFoundException {
+        if (CLASS_CACHE.containsKey(impl.getKey())) {
+            return Pair.of((Class<T>) CLASS_CACHE.get(impl.getKey()), true);
+        }
+
+        Class<?> clazz;
+        switch (impl.getEngine()) {
+            case GROOVY:
+                clazz = GROOVY_CLASSLOADER.parseClass(impl.getBody());
+                break;
+
+            case JAVA:
+            default:
+                clazz = Class.forName(impl.getBody());
+        }
+
+        CLASS_CACHE.put(impl.getKey(), clazz);
+        return Pair.of((Class<T>) clazz, false);
+    }
+
+    @SuppressWarnings("unchecked")
+    public static <T> T build(final Implementation impl) throws ClassNotFoundException {
+        return (T) ApplicationContextProvider.getBeanFactory().
+                createBean(getClass(impl).getLeft(), AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <T> T build(
+            final Class<T> clazz,
+            final boolean classCached,
+            final Supplier<T> cacheGetter,
+            final Consumer<T> cachePutter) {
+
+        boolean perContext = Optional.ofNullable(clazz.getAnnotation(SyncopeImplementation.class)).
+                map(ann -> ann.scope() == InstanceScope.PER_CONTEXT).
+                orElse(true);
+        T instance = null;
+        if (perContext && classCached) {
+            instance = cacheGetter.get();
+        }
+        if (instance == null) {
+            instance = (T) ApplicationContextProvider.getBeanFactory().
+                    createBean(clazz, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+
+            if (perContext) {
+                cachePutter.accept(instance);
+            }
+        }
+
+        return instance;
+    }
+
+    public static <T> T build(final Implementation impl, final Supplier<T> cacheGetter, final Consumer<T> cachePutter)
+            throws ClassNotFoundException {
+
+        Pair<Class<T>, Boolean> clazz = getClass(impl);
+
+        return build(clazz.getLeft(), clazz.getRight(), cacheGetter, cachePutter);
+    }
+
+    public static Class<?> purge(final String implementation) {
+        return CLASS_CACHE.remove(implementation);
+    }
+
+    private ImplementationManager() {
+        // private constructor for static utility class
+    }
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/InstanceScope.java
similarity index 64%
copy from core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java
copy to core/spring/src/main/java/org/apache/syncope/core/spring/implementation/InstanceScope.java
index 5b49b4c..fb76a83 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/InstanceScope.java
@@ -16,14 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.persistence.api.attrvalue.validation;
+package org.apache.syncope.core.spring.implementation;
 
-import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
-import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+public enum InstanceScope {
+    /**
+     * The declaring Implementation will be instantiated every time it is getting invoked.
+     */
+    PER_CALL,
+    /**
+     * The declaring Implementation will be instantiated once, by the time of first invocation; such instance will
+     * not be destroyed until the Spring Context gets refreshed or shut down.
+     */
+    PER_CONTEXT
 
-public interface Validator {
-
-    void setSchema(PlainSchema schema);
-
-    void validate(String value, PlainAttrValue attrValue);
 }
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/SyncopeImplementation.java
similarity index 67%
copy from core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java
copy to core/spring/src/main/java/org/apache/syncope/core/spring/implementation/SyncopeImplementation.java
index 5b49b4c..30e8ee7 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/attrvalue/validation/Validator.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/SyncopeImplementation.java
@@ -16,14 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.persistence.api.attrvalue.validation;
+package org.apache.syncope.core.spring.implementation;
 
-import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
-import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 
-public interface Validator {
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SyncopeImplementation {
 
-    void setSchema(PlainSchema schema);
-
-    void validate(String value, PlainAttrValue attrValue);
+    InstanceScope scope() default InstanceScope.PER_CONTEXT;
 }
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultPasswordGenerator.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultPasswordGenerator.java
index 3499bea..daa3351 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultPasswordGenerator.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/DefaultPasswordGenerator.java
@@ -20,9 +20,13 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
+import org.apache.syncope.core.persistence.api.dao.PasswordRule;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.spring.ImplementationManager;
 import org.apache.syncope.core.spring.policy.DefaultPasswordRule;
@@ -48,6 +52,8 @@
 
     protected static final int MIN_LENGTH_IF_ZERO = 8;
 
+    protected final Map<String, PasswordRule> perContextPasswordRules = new ConcurrentHashMap<>();
+
     @Transactional(readOnly = true)
     @Override
     public String generate(final ExternalResource resource) {
@@ -60,21 +66,31 @@
         return generate(policies);
     }
 
+    protected List<PasswordRule> getPasswordRules(final PasswordPolicy policy) {
+        List<PasswordRule> result = new ArrayList<>();
+
+        for (Implementation impl : policy.getRules()) {
+            try {
+                ImplementationManager.buildPasswordRule(
+                        impl,
+                        () -> perContextPasswordRules.get(impl.getKey()),
+                        instance -> perContextPasswordRules.put(impl.getKey(), instance)).
+                        ifPresent(result::add);
+            } catch (Exception e) {
+                LOG.warn("While building {}", impl, e);
+            }
+        }
+
+        return result;
+    }
+
     @Override
     public String generate(final List<PasswordPolicy> policies) {
         List<DefaultPasswordRuleConf> ruleConfs = new ArrayList<>();
 
-        policies.stream().forEach(policy -> policy.getRules().forEach(impl -> {
-            try {
-                ImplementationManager.buildPasswordRule(impl).ifPresent(rule -> {
-                    if (rule.getConf() instanceof DefaultPasswordRuleConf) {
-                        ruleConfs.add((DefaultPasswordRuleConf) rule.getConf());
-                    }
-                });
-            } catch (Exception e) {
-                LOG.error("Invalid {}, ignoring...", impl, e);
-            }
-        }));
+        policies.stream().forEach(policy -> getPasswordRules(policy).stream().
+                filter(rule -> rule.getConf() instanceof DefaultPasswordRuleConf).
+                forEach(rule -> ruleConfs.add((DefaultPasswordRuleConf) rule.getConf())));
 
         return generate(merge(ruleConfs));
     }
diff --git a/core/spring/src/test/java/org/apache/syncope/core/spring/ImplementationManagerTest.java b/core/spring/src/test/java/org/apache/syncope/core/spring/ImplementationManagerTest.java
index 7fe3fe9..3f3f0f8 100644
--- a/core/spring/src/test/java/org/apache/syncope/core/spring/ImplementationManagerTest.java
+++ b/core/spring/src/test/java/org/apache/syncope/core/spring/ImplementationManagerTest.java
@@ -51,8 +51,8 @@
         String body = POJOHelper.serialize(createBaseDefaultPasswordRuleConf());
 
         assertTimeoutPreemptively(Duration.ofSeconds(30), () -> {
-            TestImplementation implementation = new TestImplementation();
-            implementation.setBody(body);
+            TestImplementation impl = new TestImplementation();
+            impl.setBody(body);
             ReentrantLock lock = new ReentrantLock();
             lock.lock();
             AtomicInteger runningThreads = new AtomicInteger(0);
@@ -66,7 +66,11 @@
                             Thread.yield();
                         }
                         try {
-                            ImplementationManager.buildPasswordRule(implementation).orElseThrow();
+                            ImplementationManager.buildPasswordRule(
+                                    impl,
+                                    () -> null,
+                                    instance -> {
+                                    }).orElseThrow();
                         } catch (Exception e) {
                             errorMessages.add(e.getLocalizedMessage());
                             errorCount.incrementAndGet();
diff --git a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/ElasticsearchPersistenceContext.java b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/ElasticsearchPersistenceContext.java
index 8fba443..757fb34 100644
--- a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/ElasticsearchPersistenceContext.java
+++ b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/ElasticsearchPersistenceContext.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.persistence.jpa;
 
 import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
@@ -56,6 +57,7 @@
             final PlainSchemaDAO schemaDAO,
             final EntityFactory entityFactory,
             final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator,
             final ElasticsearchClient client,
             final @Lazy ElasticsearchUtils elasticsearchUtils) {
 
@@ -68,6 +70,7 @@
                 schemaDAO,
                 entityFactory,
                 anyUtilsFactory,
+                validator,
                 client,
                 elasticsearchUtils);
     }
diff --git a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
index d203491..d70d75e 100644
--- a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
+++ b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
@@ -48,6 +48,7 @@
 import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.rest.api.service.JAXRSService;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
@@ -116,10 +117,21 @@
             final PlainSchemaDAO schemaDAO,
             final EntityFactory entityFactory,
             final AnyUtilsFactory anyUtilsFactory,
+            final PlainAttrValidationManager validator,
             final ElasticsearchClient client,
             final ElasticsearchUtils elasticsearchUtils) {
 
-        super(realmDAO, dynRealmDAO, userDAO, groupDAO, anyObjectDAO, schemaDAO, entityFactory, anyUtilsFactory);
+        super(
+                realmDAO,
+                dynRealmDAO,
+                userDAO,
+                groupDAO,
+                anyObjectDAO,
+                schemaDAO,
+                entityFactory,
+                anyUtilsFactory,
+                validator);
+
         this.client = client;
         this.elasticsearchUtils = elasticsearchUtils;
     }
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/DateToDateItemTransformer.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/DateToDateItemTransformer.java
index 894e03c..67ddf4d 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/DateToDateItemTransformer.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/DateToDateItemTransformer.java
@@ -25,7 +25,10 @@
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
+import org.apache.syncope.core.spring.implementation.InstanceScope;
+import org.apache.syncope.core.spring.implementation.SyncopeImplementation;
 
+@SyncopeImplementation(scope = InstanceScope.PER_CONTEXT)
 public class DateToDateItemTransformer implements ItemTransformer {
 
     @Override
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/DateToLongItemTransformer.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/DateToLongItemTransformer.java
index 00a9152..03e135e 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/DateToLongItemTransformer.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/DateToLongItemTransformer.java
@@ -25,7 +25,10 @@
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
+import org.apache.syncope.core.spring.implementation.InstanceScope;
+import org.apache.syncope.core.spring.implementation.SyncopeImplementation;
 
+@SyncopeImplementation(scope = InstanceScope.PER_CONTEXT)
 public class DateToLongItemTransformer implements ItemTransformer {
 
     @Override