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

[syncope] branch 2_1_X updated: [SYNCOPE-1667] Adding PropagationPolicy (#326)

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

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


The following commit(s) were added to refs/heads/2_1_X by this push:
     new e570484  [SYNCOPE-1667] Adding PropagationPolicy (#326)
e570484 is described below

commit e570484ae601d1a1a65ebbe1c9f8ec555309d8f7
Author: Francesco Chicchiriccò <il...@users.noreply.github.com>
AuthorDate: Tue Mar 15 13:40:41 2022 +0100

    [SYNCOPE-1667] Adding PropagationPolicy (#326)
---
 .github/workflows/jpajson.yml                      |   2 +-
 .../syncope/client/console/pages/Policies.java     |  11 ++
 .../console/policies/PolicyModalPanelBuilder.java  | 124 ++++++++++++++
 .../policies/PropagationPolicyDirectoryPanel.java  |  51 ++++++
 .../wizards/resources/ResourceSecurityPanel.java   |  25 +++
 .../client/console/pages/Policies.properties       |   1 +
 .../client/console/pages/Policies_fr_CA.properties |   1 +
 .../client/console/pages/Policies_it.properties    |   1 +
 .../client/console/pages/Policies_ja.properties    |   1 +
 .../client/console/pages/Policies_pt_BR.properties |   1 +
 .../client/console/pages/Policies_ru.properties    |   1 +
 .../policies/PolicyDirectoryPanel.properties       |   2 +
 .../policies/PolicyDirectoryPanel_fr_CA.properties |  12 +-
 .../policies/PolicyDirectoryPanel_it.properties    |   2 +
 .../policies/PolicyDirectoryPanel_ja.properties    |   2 +
 .../policies/PolicyDirectoryPanel_pt_BR.properties |   2 +
 .../policies/PolicyDirectoryPanel_ru.properties    |   2 +
 .../wizards/resources/ResourceSecurityPanel.html   |   3 +
 .../resources/ResourceSecurityPanel.properties     |   1 +
 .../ResourceSecurityPanel_fr_CA.properties         |   1 +
 .../resources/ResourceSecurityPanel_it.properties  |   1 +
 .../resources/ResourceSecurityPanel_ja.properties  |   1 +
 .../ResourceSecurityPanel_pt_BR.properties         |   1 +
 .../resources/ResourceSecurityPanel_ru.properties  |   1 +
 .../common/lib/policy/PropagationPolicyTO.java     |  69 ++++++++
 .../apache/syncope/common/lib/to/ResourceTO.java   |  12 ++
 .../{PolicyType.java => BackOffStrategy.java}      |  29 ++--
 .../syncope/common/lib/types/PolicyType.java       |   4 +
 .../api/entity/policy/PropagationPolicy.java       |  39 ++---
 .../api/entity/resource/ExternalResource.java      |   7 +-
 .../src/test/resources/domains/MasterContent.xml   |   5 +-
 .../jpa/dao/JPAExternalResourceDAO.java            |   3 +
 .../core/persistence/jpa/dao/JPAPolicyDAO.java     |   6 +
 .../core/persistence/jpa/dao/JPARealmDAO.java      |  11 +-
 .../jpa/dao/JPARelationshipTypeDAO.java            |   2 +-
 .../core/persistence/jpa/dao/JPAUserDAO.java       |   7 +-
 .../persistence/jpa/entity/JPAEntityFactory.java   |   4 +
 .../jpa/entity/policy/JPAPolicyUtils.java          |   4 +
 .../jpa/entity/policy/JPAPolicyUtilsFactory.java   |   6 +
 .../jpa/entity/policy/JPAPropagationPolicy.java    |  76 +++++++++
 .../jpa/entity/resource/JPAExternalResource.java   |  16 ++
 .../core/persistence/jpa/inner/PolicyTest.java     |  75 ++++++++-
 .../core/persistence/jpa/outer/GroupTest.java      |   4 +-
 .../core/persistence/jpa/outer/ResourceTest.java   |  17 +-
 .../src/test/resources/domains/MasterContent.xml   |   5 +-
 .../api/propagation/PropagationTaskExecutor.java   |   7 +
 core/provisioning-java/pom.xml                     |   5 +
 .../java/data/PolicyDataBinderImpl.java            |  25 ++-
 .../java/data/ResourceDataBinderImpl.java          |  16 ++
 .../AbstractPropagationTaskExecutor.java           | 178 ++++++++++++++++-----
 .../PriorityPropagationTaskExecutor.java           | 107 +++++++------
 .../apache/syncope/fit/console/PoliciesITCase.java |   4 +-
 .../org/apache/syncope/fit/core/PolicyITCase.java  |  29 +++-
 .../syncope/fit/core/PropagationTaskITCase.java    |  46 ++++++
 pom.xml                                            |   8 +-
 .../reference-guide/concepts/policies.adoc         |  14 ++
 .../asciidoc/reference-guide/concepts/tasks.adoc   |   6 +-
 57 files changed, 925 insertions(+), 171 deletions(-)

diff --git a/.github/workflows/jpajson.yml b/.github/workflows/jpajson.yml
index 80a2b87..48da9c4 100644
--- a/.github/workflows/jpajson.yml
+++ b/.github/workflows/jpajson.yml
@@ -26,7 +26,7 @@ on:
     - cron: '0 13 * * 4'
 
 jobs:
-  PostgreSQL:
+  JPAJSON:
     runs-on: ubuntu-latest
 
     steps:
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/Policies.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/Policies.java
index 294b238..5688f31 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/pages/Policies.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/Policies.java
@@ -24,6 +24,7 @@ import java.util.List;
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
 import org.apache.syncope.client.console.policies.AccountPolicyDirectoryPanel;
 import org.apache.syncope.client.console.policies.PasswordPolicyDirectoryPanel;
+import org.apache.syncope.client.console.policies.PropagationPolicyDirectoryPanel;
 import org.apache.syncope.client.console.policies.PullPolicyDirectoryPanel;
 import org.apache.syncope.client.console.policies.PushPolicyDirectoryPanel;
 import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
@@ -72,6 +73,16 @@ public class Policies extends BasePage {
             }
         });
 
+        tabs.add(new AbstractTab(new ResourceModel("policy.propagation")) {
+
+            private static final long serialVersionUID = -6815067322125799251L;
+
+            @Override
+            public Panel getPanel(final String panelId) {
+                return new PropagationPolicyDirectoryPanel(panelId, getPageReference());
+            }
+        });
+
         tabs.add(new AbstractTab(new ResourceModel("policy.pull")) {
 
             private static final long serialVersionUID = -6815067322125799251L;
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java
index f4c4b43..e4497e4 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/policies/PolicyModalPanelBuilder.java
@@ -30,6 +30,7 @@ import org.apache.syncope.client.console.panels.AbstractModalPanel;
 import org.apache.syncope.client.console.panels.WizardModalPanel;
 import org.apache.syncope.client.console.rest.PolicyRestClient;
 import org.apache.syncope.client.console.rest.ResourceRestClient;
+import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior;
 import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
 import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel;
@@ -42,8 +43,10 @@ import org.apache.syncope.client.console.wizards.AjaxWizard;
 import org.apache.syncope.common.lib.policy.PolicyTO;
 import org.apache.syncope.common.lib.policy.AccountPolicyTO;
 import org.apache.syncope.common.lib.policy.PasswordPolicyTO;
+import org.apache.syncope.common.lib.policy.PropagationPolicyTO;
 import org.apache.syncope.common.lib.policy.ProvisioningPolicyTO;
 import org.apache.syncope.common.lib.to.EntityTO;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
 import org.apache.syncope.common.lib.types.PolicyType;
 import org.apache.wicket.Component;
@@ -51,6 +54,7 @@ import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.markup.html.list.ListItem;
 import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.LoadableDetachableModel;
 import org.apache.wicket.model.PropertyModel;
 import org.apache.wicket.model.util.ListModel;
@@ -59,6 +63,42 @@ public class PolicyModalPanelBuilder<T extends PolicyTO> extends AbstractModalPa
 
     private static final long serialVersionUID = 5945391813567245081L;
 
+    private static class BackOffParamsModel<N extends Number> implements IModel<N> {
+
+        private static final long serialVersionUID = 28839546672164L;
+
+        private final PropertyModel<String> backOffParamsModel;
+
+        private final int index;
+
+        BackOffParamsModel(final PropertyModel<String> backOffParamsModel, final int index) {
+            this.backOffParamsModel = backOffParamsModel;
+            this.index = index;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public N getObject() {
+            String[] split = backOffParamsModel.getObject().split(";");
+            if (index >= split.length) {
+                return null;
+            }
+
+            return index == 2
+                    ? (N) Double.valueOf(backOffParamsModel.getObject().split(";")[index])
+                    : (N) Long.valueOf(backOffParamsModel.getObject().split(";")[index]);
+        }
+
+        @Override
+        public void setObject(final N object) {
+            String[] split = backOffParamsModel.getObject().split(";");
+            if (index < split.length) {
+                split[index] = object.toString();
+                backOffParamsModel.setObject(Arrays.stream(split).collect(Collectors.joining(";")));
+            }
+        }
+    }
+
     private final BaseModal<T> modal;
 
     private final PolicyType type;
@@ -136,6 +176,61 @@ public class PolicyModalPanelBuilder<T extends PolicyTO> extends AbstractModalPa
                         "allowNullPassword",
                         new PropertyModel<>(policyTO, "allowNullPassword"),
                         false));
+            } else if (policyTO instanceof PropagationPolicyTO) {
+                fields.add(new AjaxSpinnerFieldPanel.Builder<Integer>().build(
+                        "field",
+                        "maxAttempts",
+                        Integer.class,
+                        new PropertyModel<>(policyTO, "maxAttempts")));
+                AjaxDropDownChoicePanel<Serializable> backOffStrategy = new AjaxDropDownChoicePanel<>(
+                        "field",
+                        "backOffStrategy",
+                        new PropertyModel<>(policyTO, "backOffStrategy")).
+                        setChoices(Arrays.asList((Serializable[]) BackOffStrategy.values()));
+                fields.add(backOffStrategy);
+
+                PropertyModel<String> backOffParamsModel = new PropertyModel<>(policyTO, "backOffParams");
+
+                AjaxSpinnerFieldPanel<Long> initialInterval = new AjaxSpinnerFieldPanel.Builder<Long>().
+                        min(1L).build(
+                        "field",
+                        "initialInterval",
+                        Long.class,
+                        new BackOffParamsModel<>(backOffParamsModel, 0));
+                fields.add(initialInterval.setOutputMarkupPlaceholderTag(true));
+                AjaxSpinnerFieldPanel<Long> maxInterval = new AjaxSpinnerFieldPanel.Builder<Long>().
+                        min(1L).build(
+                        "field",
+                        "maxInterval",
+                        Long.class,
+                        new BackOffParamsModel<>(backOffParamsModel, 1));
+                fields.add(maxInterval.setOutputMarkupPlaceholderTag(true).setVisible(false));
+                AjaxSpinnerFieldPanel<Double> multiplier = new AjaxSpinnerFieldPanel.Builder<Double>().
+                        min(1D).build(
+                        "field",
+                        "multiplier",
+                        Double.class,
+                        new BackOffParamsModel<>(backOffParamsModel, 2));
+                fields.add(multiplier.setOutputMarkupPlaceholderTag(true).setVisible(false));
+
+                showHide(backOffStrategy, initialInterval, maxInterval, multiplier);
+
+                backOffStrategy.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+                    private static final long serialVersionUID = -1107858522700306810L;
+
+                    @Override
+                    protected void onUpdate(final AjaxRequestTarget target) {
+                        BackOffStrategy strategy = (BackOffStrategy) backOffStrategy.getField().getModelObject();
+                        backOffParamsModel.setObject(strategy.getDefaultBackOffParams());
+
+                        showHide(backOffStrategy, initialInterval, maxInterval, multiplier);
+
+                        target.add(initialInterval);
+                        target.add(maxInterval);
+                        target.add(multiplier);
+                    }
+                });
             } else if (policyTO instanceof ProvisioningPolicyTO) {
                 fields.add(new AjaxDropDownChoicePanel<>(
                         "field",
@@ -156,6 +251,35 @@ public class PolicyModalPanelBuilder<T extends PolicyTO> extends AbstractModalPa
             });
         }
 
+        private void showHide(
+                final AjaxDropDownChoicePanel<Serializable> backOffStrategy,
+                final AjaxSpinnerFieldPanel<Long> initialInterval,
+                final AjaxSpinnerFieldPanel<Long> maxInterval,
+                final AjaxSpinnerFieldPanel<Double> multiplier) {
+
+            BackOffStrategy strategy = (BackOffStrategy) backOffStrategy.getField().getModelObject();
+
+            switch (strategy) {
+                case EXPONENTIAL:
+                    initialInterval.addLabel("initialInterval");
+                    maxInterval.setVisible(true);
+                    multiplier.setVisible(true);
+                    break;
+
+                case RANDOM:
+                    initialInterval.addLabel("initialInterval");
+                    maxInterval.setVisible(true);
+                    multiplier.setVisible(true);
+                    break;
+
+                case FIXED:
+                default:
+                    initialInterval.addLabel("period");
+                    maxInterval.setVisible(false);
+                    multiplier.setVisible(false);
+            }
+        }
+
         @Override
         public void onSubmit(final AjaxRequestTarget target) {
             try {
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/policies/PropagationPolicyDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/policies/PropagationPolicyDirectoryPanel.java
new file mode 100644
index 0000000..4489f22
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/policies/PropagationPolicyDirectoryPanel.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.client.console.policies;
+
+import java.util.List;
+import org.apache.syncope.common.lib.policy.PropagationPolicyTO;
+import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.model.StringResourceModel;
+
+public class PropagationPolicyDirectoryPanel extends PolicyDirectoryPanel<PropagationPolicyTO> {
+
+    private static final long serialVersionUID = 25188602686577L;
+
+    public PropagationPolicyDirectoryPanel(final String id, final PageReference pageRef) {
+        super(id, PolicyType.PROPAGATION, pageRef);
+
+        PropagationPolicyTO defaultItem = new PropagationPolicyTO();
+
+        this.addNewItemPanelBuilder(
+                new PolicyModalPanelBuilder<>(PolicyType.PROPAGATION, defaultItem, modal, pageRef), true);
+        MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, StandardEntitlement.POLICY_CREATE);
+
+        initResultTable();
+    }
+
+    @Override
+    protected void addCustomColumnFields(final List<IColumn<PropagationPolicyTO, String>> columns) {
+        columns.add(new PropertyColumn<>(new StringResourceModel("maxAttempts", this), "maxAttempts", "maxAttempts"));
+    }
+}
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java
index 755c11e..952fdf0 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.java
@@ -63,6 +63,17 @@ public class ResourceSecurityPanel extends WizardStep {
         }
     };
 
+    private final IModel<Map<String, String>> propagationPolicies = new LoadableDetachableModel<Map<String, String>>() {
+
+        private static final long serialVersionUID = -2012833443695917883L;
+
+        @Override
+        protected Map<String, String> load() {
+            return policyRestClient.getPolicies(PolicyType.PROPAGATION).stream().
+                    collect(Collectors.toMap(PolicyTO::getKey, PolicyTO::getDescription));
+        }
+    };
+
     private final IModel<Map<String, String>> pullPolicies = new LoadableDetachableModel<Map<String, String>>() {
 
         private static final long serialVersionUID = -2012833443695917883L;
@@ -123,6 +134,20 @@ public class ResourceSecurityPanel extends WizardStep {
         // -------------------------------
 
         // -------------------------------
+        // Propagation policy selection
+        // -------------------------------
+        AjaxDropDownChoicePanel<String> propagationPolicy = new AjaxDropDownChoicePanel<>(
+                "propagationPolicy",
+                new ResourceModel("propagationPolicy", "propagationPolicy").getObject(),
+                new PropertyModel<>(resourceTO, "propagationPolicy"),
+                false);
+        propagationPolicy.setChoiceRenderer(new PolicyRenderer(propagationPolicies));
+        propagationPolicy.setChoices(new ArrayList<>(propagationPolicies.getObject().keySet()));
+        ((DropDownChoice<?>) propagationPolicy.getField()).setNullValid(true);
+        container.add(propagationPolicy);
+        // -------------------------------
+
+        // -------------------------------
         // Pull policy selection
         // -------------------------------
         AjaxDropDownChoicePanel<String> pullPolicy = new AjaxDropDownChoicePanel<>(
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies.properties
index b8dd372..85928d6 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies.properties
@@ -18,3 +18,4 @@ policy.account=Account
 policy.password=Password
 policy.pull=Pull
 policy.push=Push
+policy.propagation=Propagation
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_fr_CA.properties
index d7b18cc..db7d4b7 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_fr_CA.properties
@@ -18,3 +18,4 @@ policy.account=Compte
 policy.password=Mot de passe
 policy.pull=Pull
 policy.push=Push
+policy.propagation=Propagation
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_it.properties
index b8dd372..85928d6 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_it.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_it.properties
@@ -18,3 +18,4 @@ policy.account=Account
 policy.password=Password
 policy.pull=Pull
 policy.push=Push
+policy.propagation=Propagation
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ja.properties
index db61808..a602164 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ja.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ja.properties
@@ -18,3 +18,4 @@ policy.account=\u30a2\u30ab\u30a6\u30f3\u30c8
 policy.password=\u30d1\u30b9\u30ef\u30fc\u30c9
 policy.pull=\u30d7\u30eb
 policy.push=\u30d7\u30c3\u30b7\u30e5
+policy.propagation=Propagation
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_pt_BR.properties
index b8dd372..85928d6 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_pt_BR.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_pt_BR.properties
@@ -18,3 +18,4 @@ policy.account=Account
 policy.password=Password
 policy.pull=Pull
 policy.push=Push
+policy.propagation=Propagation
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ru.properties
index 5c1a92a..899dae8 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ru.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/pages/Policies_ru.properties
@@ -19,3 +19,4 @@ policy.account=\u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u0443\u0447\u04
 policy.password=\u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u043f\u0430\u0440\u043e\u043b\u0435\u0439
 policy.pull=\u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445
 policy.push=Push
+policy.propagation=Propagation
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.properties b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.properties
index b9074b2..98d7a32 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.properties
@@ -29,3 +29,5 @@ any.finish=Submit ${description}
 any.cancel=Cancel ${description}
 compose.title=rules
 conflictResolutionAction=Conflict Resolution Action
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_fr_CA.properties
index 1207fe3..f77608e 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_fr_CA.properties
@@ -15,17 +15,19 @@
 # specific language governing permissions and limitations
 # under the License.
 description=Description
-usedByResources=Utilis� par ressource
-usedByRealms=Utilis� par domaine
+usedByResources=Utilis\u00e9 par ressource
+usedByRealms=Utilis\u00e9 par domaine
 passthroughResources=Ressources de transit
 maxAuthenticationAttempts=Nombre maximal de tentatives d'authentification
 propagateSuspension=Suspension de propagation
 historyLength=Longueur de l'historique
 allowNullPassword=Permettre mot de passe invalide
-policy.rules=R�glements ${description} 
+policy.rules=R\u00e8glements ${description} 
 any.edit=Modifier ${description}
 any.new=Nouvelle politique
 any.finish=Soumettre ${description}
 any.cancel=Annuler ${description}
-compose.title=r�glements 
-conflictResolutionAction=Action de r�solution des conflits
+compose.title=r\u00e8glements 
+conflictResolutionAction=Action de r\u00e9solution des conflits
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_it.properties
index f93168d..67fe5d4 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_it.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_it.properties
@@ -29,3 +29,5 @@ any.finish=Invia ${description}
 any.cancel=Annulla ${description}
 compose.title=regole
 conflictResolutionAction=Azione di Risoluzione Conflitti
+maxAttempts=Tentativi Massimi
+backOffStrategy=Strategia di BackOff
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ja.properties
index 54b0d64..dd6427b 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ja.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ja.properties
@@ -29,3 +29,5 @@ any.finish=${description} \u3092\u5b9f\u884c
 any.cancel=${description} \u3092\u30ad\u30e3\u30f3\u30bb\u30eb
 compose.title=\u30eb\u30fc\u30eb
 conflictResolutionAction=\u7af6\u5408\u89e3\u6c7a\u30a2\u30af\u30b7\u30e7\u30f3
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_pt_BR.properties
index b9074b2..98d7a32 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_pt_BR.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_pt_BR.properties
@@ -29,3 +29,5 @@ any.finish=Submit ${description}
 any.cancel=Cancel ${description}
 compose.title=rules
 conflictResolutionAction=Conflict Resolution Action
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ru.properties
index d161fdc..780fe78 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ru.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/policies/PolicyDirectoryPanel_ru.properties
@@ -30,3 +30,5 @@ any.finish=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c ${description}
 any.cancel=\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c ${description}
 compose.title=rules
 conflictResolutionAction=Conflict Resolution Action
+maxAttempts=Max Attempts
+backOffStrategy=BackOff Strategy
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.html b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.html
index fea39ff..00b4882 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.html
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.html
@@ -25,6 +25,9 @@ under the License.
       <span wicket:id="passwordPolicy">
         [panel for dynamic input type markup]
       </span>
+      <span wicket:id="propagationPolicy">
+        [panel for dynamic input type markup]
+      </span>
       <span wicket:id="pullPolicy">
         [panel for dynamic input type markup]
       </span>
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.properties
index e4a70ca..c2b3da7 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel.properties
@@ -18,3 +18,4 @@ passwordPolicy=Password Policy
 accountPolicy=Account Policy
 pullPolicy=Pull Policy
 pushPolicy=Push Policy
+propagationPolicy=Propagation Policy
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_fr_CA.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_fr_CA.properties
index 8a2c40f..2e20b1a 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_fr_CA.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_fr_CA.properties
@@ -18,3 +18,4 @@ passwordPolicy=Politique sur les mots de passe
 accountPolicy=Politique sur les comptes
 pullPolicy=Politique Pull
 pushPolicy=Politique Push
+propagationPolicy=Propagation Policy
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_it.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_it.properties
index e4a70ca..c2b3da7 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_it.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_it.properties
@@ -18,3 +18,4 @@ passwordPolicy=Password Policy
 accountPolicy=Account Policy
 pullPolicy=Pull Policy
 pushPolicy=Push Policy
+propagationPolicy=Propagation Policy
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ja.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ja.properties
index ca6bc82..0df90b3 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ja.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ja.properties
@@ -18,3 +18,4 @@ passwordPolicy=\u30d1\u30b9\u30ef\u30fc\u30c9\u30dd\u30ea\u30b7\u30fc
 accountPolicy=\u30a2\u30ab\u30a6\u30f3\u30c8\u30dd\u30ea\u30b7\u30fc
 pullPolicy=\u30d7\u30eb\u30dd\u30ea\u30b7\u30fc
 pushPolicy=\u30d7\u30c3\u30b7\u30e5\u30dd\u30ea\u30b7\u30fc
+propagationPolicy=Propagation Policy
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_pt_BR.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_pt_BR.properties
index 7300d71..2b8b4b7 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_pt_BR.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_pt_BR.properties
@@ -18,3 +18,4 @@ passwordPolicy=Pol\u00edtica de Senha
 accountPolicy=Pol\u00edtica de Conta
 pullPolicy=Pol\u00edtica de Pull
 pushPolicy=Push Policy
+propagationPolicy=Propagation Policy
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ru.properties b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ru.properties
index 7d70763..70bf684 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ru.properties
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wizards/resources/ResourceSecurityPanel_ru.properties
@@ -19,3 +19,4 @@ passwordPolicy = \u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u043f\u0430\u
 accountPolicy = \u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u0443\u0447\u0435\u0442\u043d\u044b\u0445 \u0437\u0430\u043f\u0438\u0441\u0435\u0439
 pullPolicy=\u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0430 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445
 pushPolicy=Push Policy
+propagationPolicy=Propagation Policy
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/policy/PropagationPolicyTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/policy/PropagationPolicyTO.java
new file mode 100644
index 0000000..970edce
--- /dev/null
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/policy/PropagationPolicyTO.java
@@ -0,0 +1,69 @@
+/*
+ * 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.common.lib.policy;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import javax.xml.bind.annotation.XmlTransient;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
+
+@Schema(allOf = { PolicyTO.class })
+public class PropagationPolicyTO extends PolicyTO {
+
+    private static final long serialVersionUID = 10604950933449L;
+
+    private BackOffStrategy backOffStrategy = BackOffStrategy.FIXED;
+
+    private String backOffParams = BackOffStrategy.FIXED.getDefaultBackOffParams();
+
+    private int maxAttempts = 3;
+
+    @XmlTransient
+    @JsonProperty("@class")
+    @Schema(name = "@class", required = true, example = "org.apache.syncope.common.lib.policy.PropagationPolicyTO")
+    @Override
+    public String getDiscriminator() {
+        return getClass().getName();
+    }
+
+    public BackOffStrategy getBackOffStrategy() {
+        return backOffStrategy;
+    }
+
+    public void setBackOffStrategy(final BackOffStrategy backOffStrategy) {
+        this.backOffStrategy = backOffStrategy;
+        this.backOffParams = backOffStrategy.getDefaultBackOffParams();
+    }
+
+    public String getBackOffParams() {
+        return backOffParams;
+    }
+
+    public void setBackOffParams(final String backOffParams) {
+        this.backOffParams = backOffParams;
+    }
+
+    public int getMaxAttempts() {
+        return maxAttempts;
+    }
+
+    public void setMaxAttempts(final int maxAttempts) {
+        this.maxAttempts = maxAttempts;
+    }
+}
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
index 20a8628..12385c1 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
@@ -76,6 +76,8 @@ public class ResourceTO implements EntityTO {
 
     private String accountPolicy;
 
+    private String propagationPolicy;
+
     private String pullPolicy;
 
     private String pushPolicy;
@@ -181,6 +183,14 @@ public class ResourceTO implements EntityTO {
         this.accountPolicy = accountPolicy;
     }
 
+    public String getPropagationPolicy() {
+        return propagationPolicy;
+    }
+
+    public void setPropagationPolicy(final String propagationPolicy) {
+        this.propagationPolicy = propagationPolicy;
+    }
+
     public String getPullPolicy() {
         return pullPolicy;
     }
@@ -292,6 +302,7 @@ public class ResourceTO implements EntityTO {
                 append(provisioningTraceLevel, other.provisioningTraceLevel).
                 append(passwordPolicy, other.passwordPolicy).
                 append(accountPolicy, other.accountPolicy).
+                append(propagationPolicy, other.propagationPolicy).
                 append(pullPolicy, other.pullPolicy).
                 append(pushPolicy, other.pushPolicy).
                 append(confOverride, other.confOverride).
@@ -317,6 +328,7 @@ public class ResourceTO implements EntityTO {
                 append(provisioningTraceLevel).
                 append(passwordPolicy).
                 append(accountPolicy).
+                append(propagationPolicy).
                 append(pullPolicy).
                 append(pushPolicy).
                 append(confOverride).
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/BackOffStrategy.java
similarity index 71%
copy from common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to common/lib/src/main/java/org/apache/syncope/common/lib/types/BackOffStrategy.java
index ea429c2..0614332 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/BackOffStrategy.java
@@ -21,23 +21,18 @@ package org.apache.syncope.common.lib.types;
 import javax.xml.bind.annotation.XmlEnum;
 
 @XmlEnum
-public enum PolicyType {
+public enum BackOffStrategy {
+    FIXED("1000"),
+    EXPONENTIAL("100;30000;2"),
+    RANDOM("100;30000;2");
 
-    /**
-     * How username values should look like.
-     */
-    ACCOUNT,
-    /**
-     * How password values should look like.
-     */
-    PASSWORD,
-    /**
-     * For handling conflicts resolution during pull.
-     */
-    PULL,
-    /**
-     * For handling conflicts resolution during push.
-     */
-    PUSH;
+    private final String defaultBackOffParams;
 
+    BackOffStrategy(final String defaultBackOffParams) {
+        this.defaultBackOffParams = defaultBackOffParams;
+    }
+
+    public String getDefaultBackOffParams() {
+        return defaultBackOffParams;
+    }
 }
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
index ea429c2..20ecf69 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
@@ -32,6 +32,10 @@ public enum PolicyType {
      */
     PASSWORD,
     /**
+     * For handling propagation behavior.
+     */
+    PROPAGATION,
+    /**
      * For handling conflicts resolution during pull.
      */
     PULL,
diff --git a/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/PropagationPolicy.java
similarity index 65%
copy from common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/PropagationPolicy.java
index ea429c2..d4e8172 100644
--- a/common/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/PropagationPolicy.java
@@ -16,28 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
-
-import javax.xml.bind.annotation.XmlEnum;
-
-@XmlEnum
-public enum PolicyType {
-
-    /**
-     * How username values should look like.
-     */
-    ACCOUNT,
-    /**
-     * How password values should look like.
-     */
-    PASSWORD,
-    /**
-     * For handling conflicts resolution during pull.
-     */
-    PULL,
-    /**
-     * For handling conflicts resolution during push.
-     */
-    PUSH;
+package org.apache.syncope.core.persistence.api.entity.policy;
 
+import org.apache.syncope.common.lib.types.BackOffStrategy;
+
+public interface PropagationPolicy extends Policy {
+
+    BackOffStrategy getBackOffStrategy();
+
+    void setBackOffStrategy(BackOffStrategy backOffStrategy);
+
+    String getBackOffParams();
+
+    void setBackOffParams(String backOffParams);
+
+    int getMaxAttempts();
+
+    void setMaxAttempts(int maxAttempts);
 }
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java
index 1bb44d9..86fb038 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java
@@ -30,6 +30,7 @@ import org.apache.syncope.core.persistence.api.entity.ConnInstance;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.ProvidedKeyEntity;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
 import org.identityconnectors.framework.common.objects.ObjectClass;
@@ -58,12 +59,16 @@ public interface ExternalResource extends ProvidedKeyEntity {
 
     void setPasswordPolicy(PasswordPolicy passwordPolicy);
 
+    PropagationPolicy getPropagationPolicy();
+
+    void setPropagationPolicy(PropagationPolicy propagationPolicy);
+
     PullPolicy getPullPolicy();
 
     void setPullPolicy(PullPolicy pullPolicy);
 
     PushPolicy getPushPolicy();
-    
+
     Implementation getProvisionSorter();
 
     void setProvisionSorter(Implementation provisionSorter);
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 bca97e3..591eb2c 100644
--- a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
@@ -103,6 +103,8 @@ under the License.
   <Implementation id="DefaultPasswordRuleConf3" type="PASSWORD_RULE" engine="JAVA"
                   body='{"@class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":10,"nonAlphanumericRequired":true,"alphanumericRequired":false,"digitRequired":true,"lowercaseRequired":true,"uppercaseRequired":true,"mustStartWithDigit":true,"mustntStartWithDigit":false,"mustEndWithDigit":true,"mustntEndWithDigit":false,"mustStartWithNonAlpha":false,"mustStartWithAlpha":false,"mustntStartWithNonAlpha":false,"mustntStartWithAlpha":false,"mustEndWit [...]
   <PasswordPolicyRule policy_id="55e5de0b-c79c-4e66-adda-251b6fb8579a" implementation_id="DefaultPasswordRuleConf3"/>
+  <PropagationPolicy id="89d322db-9878-420c-b49c-67be13df9a12" description="sample propagation policy"
+                     maxAttempts="5" backOffStrategy="FIXED" backOffParams="10000"/>
 
   <RelationshipType id="inclusion" description="Models the act that an object is included in another"/>
   <RelationshipType id="neighborhood" description="Models the act that an object is near another"/>
@@ -704,7 +706,8 @@ under the License.
 
   <ExternalResource id="resource-db-scripted" connector_id="a6d017fd-a705-4507-bb7c-6ab6a6745997"
                     randomPwdIfNotProvided="0" createTraceLevel="ALL" deleteTraceLevel="ALL" provisioningTraceLevel="ALL" updateTraceLevel="ALL"
-                    enforceMandatoryCondition="0" overrideCapabilities="0"/>
+                    enforceMandatoryCondition="0" overrideCapabilities="0"
+                    propagationPolicy_id="89d322db-9878-420c-b49c-67be13df9a12"/>
 
   <ExternalResource id="rest-target-resource" connector_id="44c02549-19c3-483c-8025-4919c3283c37"
                     createTraceLevel="ALL" updateTraceLevel="ALL"  deleteTraceLevel="ALL" provisioningTraceLevel="ALL" 
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAExternalResourceDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAExternalResourceDAO.java
index 4990ee0..9512483 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAExternalResourceDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAExternalResourceDAO.java
@@ -44,6 +44,7 @@ import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
@@ -157,6 +158,8 @@ public class JPAExternalResourceDAO extends AbstractDAO<ExternalResource> implem
             query.append("accountPolicy");
         } else if (PasswordPolicy.class.isAssignableFrom(policyClass)) {
             query.append("passwordPolicy");
+        } else if (PropagationPolicy.class.isAssignableFrom(policyClass)) {
+            query.append("propagationPolicy");
         } else if (PullPolicy.class.isAssignableFrom(policyClass)) {
             query.append("pullPolicy");
         } else if (PushPolicy.class.isAssignableFrom(policyClass)) {
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java
index c1cb136..bcfee40 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java
@@ -28,12 +28,14 @@ import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.AbstractPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccountPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPasswordPolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPropagationPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushPolicy;
@@ -57,6 +59,8 @@ public class JPAPolicyDAO extends AbstractDAO<Policy> implements PolicyDAO {
                 ? JPAAccountPolicy.class
                 : PasswordPolicy.class.isAssignableFrom(reference)
                 ? JPAPasswordPolicy.class
+                : PropagationPolicy.class.isAssignableFrom(reference)
+                ? JPAPropagationPolicy.class
                 : PullPolicy.class.isAssignableFrom(reference)
                 ? JPAPullPolicy.class
                 : PushPolicy.class.isAssignableFrom(reference)
@@ -155,6 +159,8 @@ public class JPAPolicyDAO extends AbstractDAO<Policy> implements PolicyDAO {
                 resource.setAccountPolicy(null);
             } else if (policy instanceof PasswordPolicy) {
                 resource.setPasswordPolicy(null);
+            } else if (policy instanceof PropagationPolicy) {
+                resource.setPropagationPolicy(null);
             } else if (policy instanceof PullPolicy) {
                 resource.setPullPolicy(null);
             } else if (policy instanceof PushPolicy) {
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
index 97f0679..5a16853 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
@@ -34,6 +34,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.ProvisioningPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.jpa.entity.JPARealm;
@@ -144,8 +145,8 @@ public class JPARealmDAO extends AbstractDAO<Realm> implements RealmDAO {
 
     @Override
     public <T extends Policy> List<Realm> findByPolicy(final T policy) {
-        if (ProvisioningPolicy.class.isAssignableFrom(policy.getClass())) {
-            return Collections.<Realm>emptyList();
+        if (policy instanceof PropagationPolicy || policy instanceof ProvisioningPolicy) {
+            return Collections.emptyList();
         }
 
         TypedQuery<Realm> query = entityManager().createQuery(
@@ -154,10 +155,10 @@ public class JPARealmDAO extends AbstractDAO<Realm> implements RealmDAO {
         query.setParameter("policy", policy);
 
         List<Realm> result = new ArrayList<>();
-        query.getResultList().stream().map(realm -> {
+        query.getResultList().forEach(realm -> {
             result.add(realm);
-            return realm;
-        }).forEachOrdered(realm -> result.addAll(findSamePolicyChildren(realm, policy)));
+            result.addAll(findSamePolicyChildren(realm, policy));
+        });
 
         return result;
     }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARelationshipTypeDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARelationshipTypeDAO.java
index 4d7f64e..527bdc8 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARelationshipTypeDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARelationshipTypeDAO.java
@@ -90,7 +90,7 @@ public class JPARelationshipTypeDAO extends AbstractDAO<RelationshipType> implem
             }
             relationship.setLeftEnd(null);
             return relationship;
-        }).forEachOrdered(relationship -> entityManager().remove(relationship));
+        }).forEach(entityManager()::remove);
 
         entityManager().remove(type);
     }
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 3e5892c..d8169f1 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
@@ -289,6 +289,7 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
         return query.getResultList();
     }
 
+    @Override
     public List<String> findAllKeys(final int page, final int itemsPerPage) {
         return findAllKeys(JPAUser.TABLE, page, itemsPerPage);
     }
@@ -300,13 +301,13 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
         findAllResources(user).stream().
                 map(resource -> resource.getAccountPolicy()).
                 filter(policy -> policy != null).
-                forEachOrdered(policy -> policies.add(policy));
+                forEach(policy -> policies.add(policy));
 
         // add realm policies
         realmDAO.findAncestors(user.getRealm()).stream().
                 map(realm -> realm.getAccountPolicy()).
                 filter(policy -> policy != null).
-                forEachOrdered(policy -> policies.add(policy));
+                forEach(policy -> policies.add(policy));
 
         return policies;
     }
@@ -513,7 +514,7 @@ public class JPAUserDAO extends AbstractAnyDAO<User> implements UserDAO {
         query.getResultList().stream().map(resultKey -> resultKey instanceof Object[]
                 ? (String) ((Object[]) resultKey)[0]
                 : ((String) resultKey)).
-                forEachOrdered(roleKey -> {
+                forEach(roleKey -> {
                     Role role = roleDAO.find(roleKey.toString());
                     if (role == null) {
                         LOG.error("Could not find role {}, even though returned by the native query", roleKey);
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
index 048105c..9107c0e 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
@@ -134,6 +134,7 @@ import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.Privilege;
 import org.apache.syncope.core.persistence.api.entity.Remediation;
 import org.apache.syncope.core.persistence.api.entity.SchemaLabel;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.OrgUnitItem;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.jpa.entity.resource.JPAOrgUnitItem;
@@ -144,6 +145,7 @@ import org.apache.syncope.core.persistence.api.entity.user.LAPlainAttrUniqueValu
 import org.apache.syncope.core.persistence.api.entity.user.LAPlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
 import org.apache.syncope.core.persistence.jpa.dao.JPAAnySearchDAO;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPropagationPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPALAPlainAttr;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPALAPlainAttrUniqueValue;
@@ -174,6 +176,8 @@ public class JPAEntityFactory implements EntityFactory {
             result = (E) new JPAPasswordPolicy();
         } else if (reference.equals(PushPolicy.class)) {
             result = (E) new JPAPushPolicy();
+        } else if (reference.equals(PropagationPolicy.class)) {
+            result = (E) new JPAPropagationPolicy();
         } else if (reference.equals(PullPolicy.class)) {
             result = (E) new JPAPullPolicy();
         } else if (reference.equals(PullCorrelationRuleEntity.class)) {
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtils.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtils.java
index d87325e..926b978 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtils.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtils.java
@@ -23,6 +23,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
 import org.apache.syncope.core.persistence.api.entity.policy.PolicyUtils;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
 
@@ -48,6 +49,9 @@ public class JPAPolicyUtils implements PolicyUtils {
             case PASSWORD:
                 return PasswordPolicy.class;
 
+            case PROPAGATION:
+                return PropagationPolicy.class;
+
             case PULL:
                 return PullPolicy.class;
 
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtilsFactory.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtilsFactory.java
index 2a616c2..bde3b7d 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtilsFactory.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtilsFactory.java
@@ -21,6 +21,7 @@ package org.apache.syncope.core.persistence.jpa.entity.policy;
 import org.apache.syncope.common.lib.policy.AccountPolicyTO;
 import org.apache.syncope.common.lib.policy.PasswordPolicyTO;
 import org.apache.syncope.common.lib.policy.PolicyTO;
+import org.apache.syncope.common.lib.policy.PropagationPolicyTO;
 import org.apache.syncope.common.lib.policy.PullPolicyTO;
 import org.apache.syncope.common.lib.policy.PushPolicyTO;
 import org.apache.syncope.common.lib.types.PolicyType;
@@ -29,6 +30,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
 import org.apache.syncope.core.persistence.api.entity.policy.PolicyUtils;
 import org.apache.syncope.core.persistence.api.entity.policy.PolicyUtilsFactory;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.springframework.stereotype.Component;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
@@ -48,6 +50,8 @@ public class JPAPolicyUtilsFactory implements PolicyUtilsFactory {
             type = PolicyType.ACCOUNT;
         } else if (policy instanceof PasswordPolicy) {
             type = PolicyType.PASSWORD;
+        } else if (policy instanceof PropagationPolicy) {
+            type = PolicyType.PROPAGATION;
         } else if (policy instanceof PullPolicy) {
             type = PolicyType.PULL;
         } else if (policy instanceof PushPolicy) {
@@ -66,6 +70,8 @@ public class JPAPolicyUtilsFactory implements PolicyUtilsFactory {
             type = PolicyType.ACCOUNT;
         } else if (policyClass == PasswordPolicyTO.class) {
             type = PolicyType.PASSWORD;
+        } else if (policyClass == PropagationPolicyTO.class) {
+            type = PolicyType.PROPAGATION;
         } else if (policyClass == PullPolicyTO.class) {
             type = PolicyType.PULL;
         } else if (policyClass == PushPolicyTO.class) {
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPropagationPolicy.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPropagationPolicy.java
new file mode 100644
index 0000000..207886f
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPropagationPolicy.java
@@ -0,0 +1,76 @@
+/*
+ * 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.entity.policy;
+
+import java.util.Optional;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
+
+@Entity
+@Table(name = JPAPropagationPolicy.TABLE)
+public class JPAPropagationPolicy extends AbstractPolicy implements PropagationPolicy {
+
+    private static final long serialVersionUID = 17400846199535L;
+
+    public static final String TABLE = "PropagationPolicy";
+
+    @Enumerated(EnumType.STRING)
+    @NotNull
+    private BackOffStrategy backOffStrategy;
+
+    private String backOffParams;
+
+    @NotNull
+    private Integer maxAttempts = 3;
+
+    @Override
+    public BackOffStrategy getBackOffStrategy() {
+        return backOffStrategy;
+    }
+
+    @Override
+    public void setBackOffStrategy(final BackOffStrategy backOffStrategy) {
+        this.backOffStrategy = backOffStrategy;
+    }
+
+    @Override
+    public String getBackOffParams() {
+        return backOffParams;
+    }
+
+    @Override
+    public void setBackOffParams(final String backOffParams) {
+        this.backOffParams = backOffParams;
+    }
+
+    @Override
+    public int getMaxAttempts() {
+        return Optional.ofNullable(maxAttempts).orElse(3);
+    }
+
+    @Override
+    public void setMaxAttempts(final int maxAttempts) {
+        this.maxAttempts = maxAttempts;
+    }
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java
index 6ab0395..14edc8f 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAExternalResource.java
@@ -55,6 +55,7 @@ import org.apache.syncope.core.persistence.jpa.validation.entity.ExternalResourc
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccountPolicy;
@@ -66,6 +67,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit;
 import org.apache.syncope.core.persistence.jpa.entity.AbstractProvidedKeyEntity;
 import org.apache.syncope.core.persistence.jpa.entity.JPAImplementation;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPropagationPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushPolicy;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 
@@ -133,6 +135,9 @@ public class JPAExternalResource extends AbstractProvidedKeyEntity implements Ex
     private JPAAccountPolicy accountPolicy;
 
     @ManyToOne(fetch = FetchType.EAGER)
+    private JPAPropagationPolicy propagationPolicy;
+
+    @ManyToOne(fetch = FetchType.EAGER)
     private JPAPullPolicy pullPolicy;
 
     @ManyToOne(fetch = FetchType.EAGER)
@@ -313,6 +318,17 @@ public class JPAExternalResource extends AbstractProvidedKeyEntity implements Ex
     }
 
     @Override
+    public PropagationPolicy getPropagationPolicy() {
+        return propagationPolicy;
+    }
+
+    @Override
+    public void setPropagationPolicy(final PropagationPolicy propagationPolicy) {
+        checkType(propagationPolicy, JPAPropagationPolicy.class);
+        this.propagationPolicy = (JPAPropagationPolicy) propagationPolicy;
+    }
+
+    @Override
     public PullPolicy getPullPolicy() {
         return pullPolicy;
     }
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java
index e51329f..97f9e5f 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java
@@ -29,6 +29,7 @@ import java.util.UUID;
 import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
 import org.apache.syncope.common.lib.policy.DefaultPullCorrelationRuleConf;
 import org.apache.syncope.common.lib.policy.DefaultPushCorrelationRuleConf;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
 import org.apache.syncope.common.lib.types.ImplementationEngine;
 import org.apache.syncope.common.lib.types.ImplementationType;
@@ -45,6 +46,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 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.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
@@ -70,6 +73,12 @@ public class PolicyTest extends AbstractTest {
 
     @Test
     public void findByKey() {
+        PropagationPolicy propagationPolicy = policyDAO.find("89d322db-9878-420c-b49c-67be13df9a12");
+        assertNotNull(propagationPolicy);
+        assertEquals(BackOffStrategy.FIXED, propagationPolicy.getBackOffStrategy());
+        assertEquals("10000", propagationPolicy.getBackOffParams());
+        assertEquals(5, propagationPolicy.getMaxAttempts());
+
         PullPolicy pullPolicy = policyDAO.find("880f8553-069b-4aed-9930-2cd53873f544");
         assertNotNull(pullPolicy);
 
@@ -102,7 +111,27 @@ public class PolicyTest extends AbstractTest {
     }
 
     @Test
-    public void create() {
+    public void createPropagation() {
+        int beforeCount = policyDAO.findAll().size();
+
+        PropagationPolicy propagationPolicy = entityFactory.newEntity(PropagationPolicy.class);
+        propagationPolicy.setDescription("Propagation policy");
+        propagationPolicy.setMaxAttempts(5);
+        propagationPolicy.setBackOffStrategy(BackOffStrategy.EXPONENTIAL);
+        propagationPolicy.setBackOffParams(propagationPolicy.getBackOffStrategy().getDefaultBackOffParams());
+
+        propagationPolicy = policyDAO.save(propagationPolicy);
+        assertNotNull(propagationPolicy);
+        assertEquals(5, propagationPolicy.getMaxAttempts());
+        assertEquals(BackOffStrategy.EXPONENTIAL, propagationPolicy.getBackOffStrategy());
+        assertEquals(BackOffStrategy.EXPONENTIAL.getDefaultBackOffParams(), propagationPolicy.getBackOffParams());
+
+        int afterCount = policyDAO.findAll().size();
+        assertEquals(afterCount, beforeCount + 1);
+    }
+
+    @Test
+    public void createPull() {
         PullPolicy policy = entityFactory.newEntity(PullPolicy.class);
         policy.setConflictResolutionAction(ConflictResolutionAction.IGNORE);
         policy.setDescription("Pull policy");
@@ -146,6 +175,50 @@ public class PolicyTest extends AbstractTest {
     }
 
     @Test
+    public void createPush() {
+        PushPolicy policy = entityFactory.newEntity(PushPolicy.class);
+        policy.setConflictResolutionAction(ConflictResolutionAction.IGNORE);
+        policy.setDescription("Push policy");
+
+        final String pushURuleName = "net.tirasa.push.correlation.TirasaURule";
+        final String pushGRuleName = "net.tirasa.push.correlation.TirasaGRule";
+
+        Implementation impl1 = entityFactory.newEntity(Implementation.class);
+        impl1.setKey(pushURuleName);
+        impl1.setEngine(ImplementationEngine.JAVA);
+        impl1.setType(ImplementationType.PUSH_CORRELATION_RULE);
+        impl1.setBody(PushCorrelationRule.class.getName());
+        impl1 = implementationDAO.save(impl1);
+
+        PushCorrelationRuleEntity rule1 = entityFactory.newEntity(PushCorrelationRuleEntity.class);
+        rule1.setAnyType(anyTypeDAO.findUser());
+        rule1.setPushPolicy(policy);
+        rule1.setImplementation(impl1);
+        policy.add(rule1);
+
+        Implementation impl2 = entityFactory.newEntity(Implementation.class);
+        impl2.setKey(pushGRuleName);
+        impl2.setEngine(ImplementationEngine.JAVA);
+        impl2.setType(ImplementationType.PUSH_CORRELATION_RULE);
+        impl2.setBody(PushCorrelationRule.class.getName());
+        impl2 = implementationDAO.save(impl2);
+
+        PushCorrelationRuleEntity rule2 = entityFactory.newEntity(PushCorrelationRuleEntity.class);
+        rule2.setAnyType(anyTypeDAO.findGroup());
+        rule2.setPushPolicy(policy);
+        rule2.setImplementation(impl2);
+        policy.add(rule2);
+
+        policy = policyDAO.save(policy);
+
+        assertNotNull(policy);
+        assertEquals(pushURuleName,
+                policy.getCorrelationRule(anyTypeDAO.findUser()).get().getImplementation().getKey());
+        assertEquals(pushGRuleName,
+                policy.getCorrelationRule(anyTypeDAO.findGroup()).get().getImplementation().getKey());
+    }
+
+    @Test
     public void update() {
         PasswordPolicy policy = policyDAO.find("ce93fcda-dc3a-4369-a7b0-a6108c261c85");
         assertNotNull(policy);
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 bd4bdd6..6467142 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
@@ -181,7 +181,7 @@ public class GroupTest extends AbstractTest {
         query.getResultList().stream().map(resultKey -> resultKey instanceof Object[]
                 ? (String) ((Object[]) resultKey)[0]
                 : ((String) resultKey)).
-                forEachOrdered(actualKey -> {
+                forEach(actualKey -> {
                     Group group = groupDAO.find(actualKey.toString());
                     if (group == null) {
                     } else if (!result.contains(group)) {
@@ -282,7 +282,7 @@ public class GroupTest extends AbstractTest {
         query.getResultList().stream().map(resultKey -> resultKey instanceof Object[]
                 ? (String) ((Object[]) resultKey)[0]
                 : ((String) resultKey)).
-                forEachOrdered(actualKey -> {
+                forEach(actualKey -> {
                     Group group = groupDAO.find(actualKey.toString());
                     if (group == null) {
                     } else if (!result.contains(group)) {
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ResourceTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ResourceTest.java
index 559a041..b08fcf8 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ResourceTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/ResourceTest.java
@@ -39,7 +39,6 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
-import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.Mapping;
@@ -212,7 +211,7 @@ public class ResourceTest extends AbstractTest {
         List<User> users = userDAO.findByResource(resource);
         assertNotNull(users);
 
-        Set<String> userKeys = users.stream().map(Entity::getKey).collect(Collectors.toSet());
+        Set<String> userKeys = users.stream().map(User::getKey).collect(Collectors.toSet());
         // -------------------------------------
 
         // Get tasks
@@ -231,14 +230,10 @@ public class ResourceTest extends AbstractTest {
         assertNull(actual);
 
         // resource must be not referenced any more from users
-        userKeys.stream().
-                map(key -> userDAO.find(key)).
-                map(actualUser -> {
-                    assertNotNull(actualUser);
-                    return actualUser;
-                }).forEachOrdered((actualUser) -> {
-            userDAO.findAllResources(actualUser).
-                    forEach(res -> assertFalse(res.getKey().equalsIgnoreCase(resource.getKey())));
+        userKeys.stream().map(userDAO::find).forEach(user -> {
+            assertNotNull(user);
+            userDAO.findAllResources(user).
+                    forEach(r -> assertFalse(r.getKey().equalsIgnoreCase(resource.getKey())));
         });
 
         // resource must be not referenced any more from the connector
@@ -264,7 +259,7 @@ public class ResourceTest extends AbstractTest {
         List<? extends MappingItem> items = ldap.getProvision(anyTypeDAO.findGroup()).get().getMapping().getItems();
         assertNotNull(items);
         assertFalse(items.isEmpty());
-        List<String> itemKeys = items.stream().map(Entity::getKey).collect(Collectors.toList());
+        List<String> itemKeys = items.stream().map(MappingItem::getKey).collect(Collectors.toList());
 
         Provision groupProvision = ldap.getProvision(anyTypeDAO.findGroup()).get();
         virSchemaDAO.findByProvision(groupProvision).
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index c33a68f..2828915 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -160,6 +160,8 @@ under the License.
   <Implementation id="DefaultPasswordRuleConf3" type="PASSWORD_RULE" engine="JAVA"
                   body='{"@class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":10,"nonAlphanumericRequired":true,"alphanumericRequired":false,"digitRequired":true,"lowercaseRequired":true,"uppercaseRequired":true,"mustStartWithDigit":true,"mustntStartWithDigit":false,"mustEndWithDigit":true,"mustntEndWithDigit":false,"mustStartWithNonAlpha":false,"mustStartWithAlpha":false,"mustntStartWithNonAlpha":false,"mustntStartWithAlpha":false,"mustEndWit [...]
   <PasswordPolicyRule policy_id="55e5de0b-c79c-4e66-adda-251b6fb8579a" implementation_id="DefaultPasswordRuleConf3"/>
+  <PropagationPolicy id="89d322db-9878-420c-b49c-67be13df9a12" description="sample propagation policy"
+                     maxAttempts="5" backOffStrategy="FIXED" backOffParams="10000"/>
 
   <RelationshipType id="inclusion" description="Models the act that an object is included in another"/>
   <RelationshipType id="neighborhood" description="Models the act that an object is near another"/>
@@ -848,7 +850,8 @@ under the License.
 
   <ExternalResource id="resource-db-scripted" connector_id="a6d017fd-a705-4507-bb7c-6ab6a6745997"
                     randomPwdIfNotProvided="0" createTraceLevel="ALL" deleteTraceLevel="ALL" provisioningTraceLevel="ALL" updateTraceLevel="ALL"
-                    enforceMandatoryCondition="0" overrideCapabilities="0"/>
+                    enforceMandatoryCondition="0" overrideCapabilities="0"
+                    propagationPolicy_id="89d322db-9878-420c-b49c-67be13df9a12"/>
 
   <ExternalResource id="rest-target-resource" connector_id="44c02549-19c3-483c-8025-4919c3283c37"
                     createTraceLevel="ALL" updateTraceLevel="ALL"  deleteTraceLevel="ALL" provisioningTraceLevel="ALL" 
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskExecutor.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskExecutor.java
index 878df57..a9c97a0 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskExecutor.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/propagation/PropagationTaskExecutor.java
@@ -43,6 +43,13 @@ public interface PropagationTaskExecutor {
     String MANDATORY_NULL_OR_EMPTY_ATTR_NAME = "__MANDATORY_NULL_OR_EMPTY__";
 
     /**
+     * Remove any RetryTemplate defined for the given External Resource from local cache.
+     *
+     * @param resource External Resource name
+     */
+    void expireRetryTemplate(String resource);
+
+    /**
      * Execute the given task and returns the generated {@link TaskExec}.
      *
      * @param taskInfo to be executed
diff --git a/core/provisioning-java/pom.xml b/core/provisioning-java/pom.xml
index 47b58c4..0e35b43 100644
--- a/core/provisioning-java/pom.xml
+++ b/core/provisioning-java/pom.xml
@@ -54,6 +54,11 @@ under the License.
     </dependency>
 
     <dependency>
+      <groupId>org.springframework.retry</groupId>
+      <artifactId>spring-retry</artifactId>
+    </dependency>
+
+    <dependency>
       <groupId>com.github.ben-manes.caffeine</groupId>
       <artifactId>caffeine</artifactId>
     </dependency>
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PolicyDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PolicyDataBinderImpl.java
index 53122b5..f185f07 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PolicyDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PolicyDataBinderImpl.java
@@ -23,6 +23,7 @@ import org.apache.syncope.core.provisioning.api.data.PolicyDataBinder;
 import org.apache.syncope.common.lib.policy.PolicyTO;
 import org.apache.syncope.common.lib.policy.AccountPolicyTO;
 import org.apache.syncope.common.lib.policy.PasswordPolicyTO;
+import org.apache.syncope.common.lib.policy.PropagationPolicyTO;
 import org.apache.syncope.common.lib.policy.PullPolicyTO;
 import org.apache.syncope.common.lib.policy.PushPolicyTO;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
@@ -39,6 +40,7 @@ import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
@@ -126,6 +128,17 @@ public class PolicyDataBinderImpl implements PolicyDataBinder {
                     accountPolicy.add(resource);
                 }
             });
+        } else if (policyTO instanceof PropagationPolicyTO) {
+            if (result == null) {
+                result = (T) entityFactory.newEntity(PropagationPolicy.class);
+            }
+
+            PropagationPolicy propagationPolicy = PropagationPolicy.class.cast(result);
+            PropagationPolicyTO propagationPolicyTO = PropagationPolicyTO.class.cast(policyTO);
+
+            propagationPolicy.setBackOffStrategy(propagationPolicyTO.getBackOffStrategy());
+            propagationPolicy.setBackOffParams(propagationPolicyTO.getBackOffParams());
+            propagationPolicy.setMaxAttempts(propagationPolicyTO.getMaxAttempts());
         } else if (policyTO instanceof PullPolicyTO) {
             if (result == null) {
                 result = (T) entityFactory.newEntity(PullPolicy.class);
@@ -157,8 +170,8 @@ public class PolicyDataBinderImpl implements PolicyDataBinder {
                 }
             });
             // remove all rules not contained in the TO
-            pullPolicy.getCorrelationRules().removeIf(anyFilter
-                    -> !pullPolicyTO.getCorrelationRules().containsKey(anyFilter.getAnyType().getKey()));
+            pullPolicy.getCorrelationRules().removeIf(anyFilter -> !pullPolicyTO.getCorrelationRules().
+                    containsKey(anyFilter.getAnyType().getKey()));
         } else if (policyTO instanceof PushPolicyTO) {
             if (result == null) {
                 result = (T) entityFactory.newEntity(PushPolicy.class);
@@ -239,6 +252,14 @@ public class PolicyDataBinderImpl implements PolicyDataBinder {
 
             accountPolicyTO.getPassthroughResources().addAll(
                     accountPolicy.getResources().stream().map(Entity::getKey).collect(Collectors.toList()));
+        } else if (policy instanceof PropagationPolicy) {
+            PropagationPolicy propagationPolicy = PropagationPolicy.class.cast(policy);
+            PropagationPolicyTO propagationPolicyTO = new PropagationPolicyTO();
+            policyTO = (T) propagationPolicyTO;
+
+            propagationPolicyTO.setBackOffStrategy(propagationPolicy.getBackOffStrategy());
+            propagationPolicyTO.setBackOffParams(propagationPolicy.getBackOffParams());
+            propagationPolicyTO.setMaxAttempts(propagationPolicy.getMaxAttempts());
         } else if (policy instanceof PullPolicy) {
             PullPolicy pullPolicy = PullPolicy.class.cast(policy);
             PullPolicyTO pullPolicyTO = new PullPolicyTO();
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 943172d..dd7773d 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
@@ -59,6 +59,7 @@ import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
+import org.apache.syncope.core.persistence.api.entity.policy.PropagationPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.Item;
@@ -68,6 +69,7 @@ import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
 import org.apache.syncope.core.provisioning.api.IntAttrName;
 import org.apache.syncope.core.provisioning.api.data.ResourceDataBinder;
+import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
 import org.identityconnectors.framework.common.objects.ObjectClass;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -106,6 +108,9 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
     @Autowired
     private IntAttrNameParser intAttrNameParser;
 
+    @Autowired
+    private PropagationTaskExecutor propagationTaskExecutor;
+
     @Override
     public ExternalResource create(final ResourceTO resourceTO) {
         return update(entityFactory.newEntity(ExternalResource.class), resourceTO);
@@ -349,6 +354,14 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
         resource.setAccountPolicy(resourceTO.getAccountPolicy() == null
                 ? null : (AccountPolicy) policyDAO.find(resourceTO.getAccountPolicy()));
 
+        if (resource.getPropagationPolicy() != null
+                && !resource.getPropagationPolicy().getKey().equals(resourceTO.getPropagationPolicy())) {
+
+            propagationTaskExecutor.expireRetryTemplate(resource.getKey());
+        }
+        resource.setPropagationPolicy(resourceTO.getPropagationPolicy() == null
+                ? null : (PropagationPolicy) policyDAO.find(resourceTO.getPropagationPolicy()));
+
         resource.setPullPolicy(resourceTO.getPullPolicy() == null
                 ? null : (PullPolicy) policyDAO.find(resourceTO.getPullPolicy()));
 
@@ -678,6 +691,9 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
         resourceTO.setAccountPolicy(resource.getAccountPolicy() == null
                 ? null : resource.getAccountPolicy().getKey());
 
+        resourceTO.setPropagationPolicy(resource.getPropagationPolicy() == null
+                ? null : resource.getPropagationPolicy().getKey());
+
         resourceTO.setPullPolicy(resource.getPullPolicy() == null
                 ? null : resource.getPullPolicy().getKey());
 
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 1730ae4..f084538 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
@@ -20,8 +20,9 @@ package org.apache.syncope.core.provisioning.java.propagation;
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
+import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -62,7 +63,6 @@ import org.apache.syncope.core.persistence.api.entity.task.TaskUtilsFactory;
 import org.apache.syncope.core.provisioning.api.AuditManager;
 import org.apache.syncope.core.provisioning.api.data.TaskDataBinder;
 import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
-import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
 import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo;
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 import org.apache.syncope.core.provisioning.java.pushpull.OutboundMatcher;
@@ -81,6 +81,12 @@ import org.identityconnectors.framework.common.objects.Uid;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.retry.RetryException;
+import org.springframework.retry.backoff.ExponentialBackOffPolicy;
+import org.springframework.retry.backoff.ExponentialRandomBackOffPolicy;
+import org.springframework.retry.backoff.FixedBackOffPolicy;
+import org.springframework.retry.policy.SimpleRetryPolicy;
+import org.springframework.retry.support.RetryTemplate;
 import org.springframework.transaction.annotation.Transactional;
 
 @Transactional(rollbackFor = { Throwable.class })
@@ -88,6 +94,8 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
 
     protected static final Logger LOG = LoggerFactory.getLogger(PropagationTaskExecutor.class);
 
+    protected final Map<String, RetryTemplate> retryTemplates = Collections.synchronizedMap(new HashMap<>());
+
     /**
      * Connector factory.
      */
@@ -157,6 +165,11 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
     @Autowired
     protected OutboundMatcher outboundMatcher;
 
+    @Override
+    public void expireRetryTemplate(final String resource) {
+        retryTemplates.remove(resource);
+    }
+
     protected List<PropagationActions> getPropagationActions(final ExternalResource resource) {
         List<PropagationActions> result = new ArrayList<>();
 
@@ -309,10 +322,116 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         return task;
     }
 
+    protected Optional<RetryTemplate> retryTemplate(final ExternalResource resource) {
+        RetryTemplate retryTemplate = null;
+
+        if (resource.getPropagationPolicy() != null) {
+            retryTemplate = retryTemplates.get(resource.getKey());
+            if (retryTemplate == null) {
+                retryTemplate = new RetryTemplate();
+
+                SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
+                retryPolicy.setMaxAttempts(resource.getPropagationPolicy().getMaxAttempts());
+                retryTemplate.setRetryPolicy(retryPolicy);
+
+                String[] params = resource.getPropagationPolicy().getBackOffParams().split(";");
+
+                switch (resource.getPropagationPolicy().getBackOffStrategy()) {
+                    case EXPONENTIAL:
+                        ExponentialBackOffPolicy eBackOffPolicy = new ExponentialBackOffPolicy();
+                        if (params.length > 0) {
+                            try {
+                                eBackOffPolicy.setInitialInterval(Long.valueOf(params[0]));
+                            } catch (NumberFormatException e) {
+                                LOG.error("Could not convert to long: {}", params[0], e);
+                            }
+                        }
+                        if (params.length > 1) {
+                            try {
+                                eBackOffPolicy.setMaxInterval(Long.valueOf(params[1]));
+                            } catch (NumberFormatException e) {
+                                LOG.error("Could not convert to long: {}", params[1], e);
+                            }
+                        }
+                        if (params.length > 2) {
+                            try {
+                                eBackOffPolicy.setMultiplier(Double.valueOf(params[2]));
+                            } catch (NumberFormatException e) {
+                                LOG.error("Could not convert to double: {}", params[2], e);
+                            }
+                        }
+                        retryTemplate.setBackOffPolicy(eBackOffPolicy);
+                        break;
+
+                    case RANDOM:
+                        ExponentialRandomBackOffPolicy erBackOffPolicy = new ExponentialRandomBackOffPolicy();
+                        if (params.length > 0) {
+                            try {
+                                erBackOffPolicy.setInitialInterval(Long.valueOf(params[0]));
+                            } catch (NumberFormatException e) {
+                                LOG.error("Could not convert to long: {}", params[0], e);
+                            }
+                        }
+                        if (params.length > 1) {
+                            try {
+                                erBackOffPolicy.setMaxInterval(Long.valueOf(params[1]));
+                            } catch (NumberFormatException e) {
+                                LOG.error("Could not convert to long: {}", params[1], e);
+                            }
+                        }
+                        if (params.length > 2) {
+                            try {
+                                erBackOffPolicy.setMultiplier(Double.valueOf(params[2]));
+                            } catch (NumberFormatException e) {
+                                LOG.error("Could not convert to double: {}", params[2], e);
+                            }
+                        }
+                        retryTemplate.setBackOffPolicy(erBackOffPolicy);
+                        break;
+
+                    case FIXED:
+                    default:
+                        FixedBackOffPolicy fBackOffPolicy = new FixedBackOffPolicy();
+                        if (params.length > 0) {
+                            try {
+                                fBackOffPolicy.setBackOffPeriod(Long.valueOf(params[0]));
+                            } catch (NumberFormatException e) {
+                                LOG.error("Could not convert to long: {}", params[0], e);
+                            }
+
+                        }
+                        retryTemplate.setBackOffPolicy(fBackOffPolicy);
+                }
+
+                retryTemplates.put(resource.getKey(), retryTemplate);
+            }
+        }
+
+        return Optional.ofNullable(retryTemplate);
+    }
+
     @Override
     public TaskExec execute(final PropagationTaskInfo taskInfo, final PropagationReporter reporter) {
         PropagationTask task = buildTask(taskInfo);
 
+        return retryTemplate(task.getResource()).map(rt -> rt.execute(context -> {
+            LOG.debug("#{} Propagation attempt", context.getRetryCount());
+
+            TaskExec exec = doExecute(taskInfo, task, reporter);
+            if (context.getRetryCount() < task.getResource().getPropagationPolicy().getMaxAttempts() - 1
+                    && !ExecStatus.SUCCESS.name().equals(exec.getStatus())) {
+
+                throw new RetryException("Attempt #" + context.getRetryCount() + " failed");
+            }
+            return exec;
+        })).orElseGet(() -> doExecute(taskInfo, task, reporter));
+    }
+
+    protected TaskExec doExecute(
+            final PropagationTaskInfo taskInfo,
+            final PropagationTask task,
+            final PropagationReporter reporter) {
+
         Connector connector = taskInfo.getConnector() == null
                 ? connFactory.getConnector(task.getResource())
                 : taskInfo.getConnector();
@@ -321,8 +440,8 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
 
         Date start = new Date();
 
-        TaskExec execution = entityFactory.newEntity(TaskExec.class);
-        execution.setStatus(ExecStatus.CREATED.name());
+        TaskExec exec = entityFactory.newEntity(TaskExec.class);
+        exec.setStatus(ExecStatus.CREATED.name());
 
         String taskExecutionMessage = null;
         String failureReason = null;
@@ -369,7 +488,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                 default:
             }
 
-            execution.setStatus(propagationAttempted.get()
+            exec.setStatus(propagationAttempted.get()
                     ? ExecStatus.SUCCESS.name()
                     : ExecStatus.NOT_ATTEMPTED.name());
 
@@ -396,14 +515,14 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
             }
 
             try {
-                execution.setStatus(ExecStatus.FAILURE.name());
+                exec.setStatus(ExecStatus.FAILURE.name());
             } catch (Exception wft) {
-                LOG.error("While executing KO action on {}", execution, wft);
+                LOG.error("While executing KO action on {}", exec, wft);
             }
 
             propagationAttempted.set(true);
 
-            actions.forEach(action -> action.onError(task, execution, e));
+            actions.forEach(action -> action.onError(task, exec, e));
         } finally {
             // Try to read remote object AFTER any actual operation
             if (connector != null) {
@@ -430,17 +549,17 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                         build();
             }
 
-            execution.setStart(start);
-            execution.setMessage(taskExecutionMessage);
-            execution.setEnd(new Date());
+            exec.setStart(start);
+            exec.setMessage(taskExecutionMessage);
+            exec.setEnd(new Date());
 
-            LOG.debug("Execution finished: {}", execution);
+            LOG.debug("Execution finished: {}", exec);
 
-            if (hasToBeregistered(task, execution)) {
-                LOG.debug("Execution to be stored: {}", execution);
+            if (hasToBeregistered(task, exec)) {
+                LOG.debug("Execution to be stored: {}", exec);
 
-                execution.setTask(task);
-                task.add(execution);
+                exec.setTask(task);
+                task.add(exec);
 
                 taskDAO.save(task);
             }
@@ -453,7 +572,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                                     ? outboundMatcher.getFIQL(beforeObj, provision)
                                     : null;
             reporter.onSuccessOrNonPriorityResourceFailures(taskInfo,
-                    ExecStatus.valueOf(execution.getStatus()),
+                    ExecStatus.valueOf(exec.getStatus()),
                     failureReason,
                     fiql,
                     beforeObj,
@@ -461,7 +580,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         }
 
         for (PropagationActions action : actions) {
-            action.after(task, execution, afterObj);
+            action.after(task, exec, afterObj);
         }
         // SYNCOPE-1136
         String anyTypeKind = task.getAnyTypeKind() == null ? "realm" : task.getAnyTypeKind().name().toLowerCase();
@@ -476,7 +595,7 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                 operation);
 
         if (notificationsAvailable || auditRequested) {
-            ExecTO execTO = taskDataBinder.getExecTO(execution);
+            ExecTO execTO = taskDataBinder.getExecTO(exec);
             notificationManager.createTasks(
                     AuthContextUtils.getWho(),
                     AuditElements.EventCategoryType.PROPAGATION,
@@ -500,12 +619,9 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
                     taskInfo);
         }
 
-        return execution;
+        return exec;
     }
 
-    protected abstract void doExecute(
-            Collection<PropagationTaskInfo> taskInfos, PropagationReporter reporter, boolean nullPriorityAsync);
-
     protected TaskExec rejected(
             final PropagationTaskInfo taskInfo,
             final String rejectReason,
@@ -540,22 +656,6 @@ public abstract class AbstractPropagationTaskExecutor implements PropagationTask
         return execution;
     }
 
-    @Override
-    public PropagationReporter execute(
-            final Collection<PropagationTaskInfo> taskInfos,
-            final boolean nullPriorityAsync) {
-
-        PropagationReporter reporter = new DefaultPropagationReporter();
-        try {
-            doExecute(taskInfos, reporter, nullPriorityAsync);
-        } catch (PropagationException e) {
-            LOG.error("Error propagation priority resource", e);
-            reporter.onPriorityResourceFailure(e.getResourceName(), taskInfos);
-        }
-
-        return reporter;
-    }
-
     /**
      * Check whether an execution has to be stored, for a given task.
      *
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 e402638..30b6f3c 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
@@ -70,71 +70,78 @@ public class PriorityPropagationTaskExecutor extends AbstractPropagationTaskExec
     }
 
     @Override
-    protected void doExecute(
+    public PropagationReporter execute(
             final Collection<PropagationTaskInfo> taskInfos,
-            final PropagationReporter reporter,
             final boolean nullPriorityAsync) {
 
-        List<PropagationTaskInfo> prioritizedTasks = taskInfos.stream().
-                filter(task -> task.getExternalResource().getPropagationPriority() != null).
-                sorted(Comparator.comparing(task -> task.getExternalResource().getPropagationPriority())).
-                collect(Collectors.toList());
-        LOG.debug("Propagation tasks sorted by priority, for serial execution: {}", prioritizedTasks);
+        PropagationReporter reporter = new DefaultPropagationReporter();
+        try {
+            List<PropagationTaskInfo> prioritizedTasks = taskInfos.stream().
+                    filter(task -> task.getExternalResource().getPropagationPriority() != null).
+                    sorted(Comparator.comparing(task -> task.getExternalResource().getPropagationPriority())).
+                    collect(Collectors.toList());
+            LOG.debug("Propagation tasks sorted by priority, for serial execution: {}", prioritizedTasks);
 
-        List<PropagationTaskInfo> concurrentTasks = taskInfos.stream().
-                filter(task -> !prioritizedTasks.contains(task)).
-                collect(Collectors.toList());
-        LOG.debug("Propagation tasks for concurrent execution: {}", concurrentTasks);
+            List<PropagationTaskInfo> concurrentTasks = taskInfos.stream().
+                    filter(task -> !prioritizedTasks.contains(task)).
+                    collect(Collectors.toList());
+            LOG.debug("Propagation tasks for concurrent execution: {}", concurrentTasks);
 
-        // first process priority resources sequentially and fail as soon as any propagation failure is reported
-        prioritizedTasks.forEach(task -> {
-            TaskExec execution = null;
-            ExecStatus execStatus;
-            String errorMessage = null;
-            try {
-                execution = newPropagationTaskCallable(task, reporter).call();
-                execStatus = ExecStatus.valueOf(execution.getStatus());
-            } catch (Exception e) {
-                LOG.error("Unexpected exception", e);
-                execStatus = ExecStatus.FAILURE;
-                errorMessage = e.getMessage();
-            }
-            if (execStatus != ExecStatus.SUCCESS) {
-                throw new PropagationException(
-                        task.getResource(),
-                        execution == null ? errorMessage : execution.getMessage());
-            }
-        });
-
-        // then process non-priority resources concurrently...
-        if (!concurrentTasks.isEmpty()) {
-            CompletionService<TaskExec> completionService = new ExecutorCompletionService<>(executor);
-            List<Future<TaskExec>> futures = new ArrayList<>();
-
-            concurrentTasks.forEach(taskInfo -> {
+            // first process priority resources sequentially and fail as soon as any propagation failure is reported
+            prioritizedTasks.forEach(task -> {
+                TaskExec execution = null;
+                ExecStatus execStatus;
+                String errorMessage = null;
                 try {
-                    futures.add(completionService.submit(newPropagationTaskCallable(taskInfo, reporter)));
-
-                    if (nullPriorityAsync) {
-                        reporter.onSuccessOrNonPriorityResourceFailures(
-                                taskInfo, ExecStatus.CREATED, null, null, null, null);
-                    }
+                    execution = newPropagationTaskCallable(task, reporter).call();
+                    execStatus = ExecStatus.valueOf(execution.getStatus());
                 } catch (Exception e) {
-                    LOG.error("While submitting task for async execution", taskInfo, e);
-                    rejected(taskInfo, e.getMessage(), reporter);
+                    LOG.error("Unexpected exception", e);
+                    execStatus = ExecStatus.FAILURE;
+                    errorMessage = e.getMessage();
+                }
+                if (execStatus != ExecStatus.SUCCESS) {
+                    throw new PropagationException(
+                            task.getResource(),
+                            execution == null ? errorMessage : execution.getMessage());
                 }
             });
 
-            // ...waiting for all callables to complete, if async processing was not required
-            if (!nullPriorityAsync) {
-                futures.forEach(future -> {
+            // then process non-priority resources concurrently...
+            if (!concurrentTasks.isEmpty()) {
+                CompletionService<TaskExec> completionService = new ExecutorCompletionService<>(executor);
+                List<Future<TaskExec>> futures = new ArrayList<>();
+
+                concurrentTasks.forEach(taskInfo -> {
                     try {
-                        future.get();
+                        futures.add(completionService.submit(newPropagationTaskCallable(taskInfo, reporter)));
+
+                        if (nullPriorityAsync) {
+                            reporter.onSuccessOrNonPriorityResourceFailures(
+                                    taskInfo, ExecStatus.CREATED, null, null, null, null);
+                        }
                     } catch (Exception e) {
-                        LOG.error("Unexpected exception", e);
+                        LOG.error("While submitting task for async execution", taskInfo, e);
+                        rejected(taskInfo, e.getMessage(), reporter);
                     }
                 });
+
+                // ...waiting for all callables to complete, if async processing was not required
+                if (!nullPriorityAsync) {
+                    futures.forEach(future -> {
+                        try {
+                            future.get();
+                        } catch (Exception e) {
+                            LOG.error("Unexpected exception", e);
+                        }
+                    });
+                }
             }
+        } catch (PropagationException e) {
+            LOG.error("Error propagation priority resource", e);
+            reporter.onPriorityResourceFailure(e.getResourceName(), taskInfos);
         }
+
+        return reporter;
     }
 }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java
index c4241ed..e1a28f1 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java
@@ -143,7 +143,7 @@ public class PoliciesITCase extends AbstractConsoleITCase {
     }
 
     private void createPullPolicy(final String description) {
-        TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:2:link");
+        TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:3:link");
         TESTER.clickLink("body:content:tabbedPanel:panel:container:content:add");
         TESTER.assertComponent("body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer", Modal.class);
 
@@ -233,7 +233,7 @@ public class PoliciesITCase extends AbstractConsoleITCase {
     }
 
     private void deletePullPolicy(final String description) {
-        TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:2:link");
+        TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:3:link");
         Component component = findComponentByProp("description", "body:content:tabbedPanel:panel:container:content:"
                 + "searchContainer:resultTable:tablePanel:groupForm:checkgroup:dataTable", description);
 
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
index e47fcf7..1be9197 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
@@ -40,9 +40,11 @@ import org.apache.syncope.common.lib.policy.PullPolicyTO;
 import org.apache.syncope.common.lib.policy.DefaultAccountRuleConf;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
+import org.apache.syncope.common.lib.policy.PropagationPolicyTO;
 import org.apache.syncope.common.lib.policy.PushPolicyTO;
 import org.apache.syncope.common.lib.types.PolicyType;
 import org.apache.syncope.common.lib.to.ImplementationTO;
+import org.apache.syncope.common.lib.types.BackOffStrategy;
 import org.apache.syncope.common.lib.types.ImplementationEngine;
 import org.apache.syncope.common.lib.types.ImplementationType;
 import org.apache.syncope.common.rest.api.RESTHeaders;
@@ -110,10 +112,13 @@ public class PolicyITCase extends AbstractITCase {
 
     @Test
     public void listByType() {
-        List<PullPolicyTO> policyTOs = policyService.list(PolicyType.PULL);
+        List<PropagationPolicyTO> propagationPolicies = policyService.list(PolicyType.PROPAGATION);
+        assertNotNull(propagationPolicies);
+        assertFalse(propagationPolicies.isEmpty());
 
-        assertNotNull(policyTOs);
-        assertFalse(policyTOs.isEmpty());
+        List<PullPolicyTO> pullPolicies = policyService.list(PolicyType.PULL);
+        assertNotNull(pullPolicies);
+        assertFalse(pullPolicies.isEmpty());
     }
 
     @Test
@@ -135,6 +140,16 @@ public class PolicyITCase extends AbstractITCase {
     }
 
     @Test
+    public void getPropagationPolicy() {
+        PropagationPolicyTO policyTO =
+                policyService.read(PolicyType.PROPAGATION, "89d322db-9878-420c-b49c-67be13df9a12");
+
+        assertNotNull(policyTO);
+        assertTrue(policyTO.getUsedByResources().contains(RESOURCE_NAME_DBSCRIPTED));
+        assertTrue(policyTO.getUsedByRealms().isEmpty());
+    }
+
+    @Test
     public void getPullPolicy() {
         PullPolicyTO policyTO = policyService.read(PolicyType.PULL, "66691e96-285f-4464-bc19-e68384ea4c85");
 
@@ -144,6 +159,14 @@ public class PolicyITCase extends AbstractITCase {
 
     @Test
     public void create() throws IOException {
+        PropagationPolicyTO propagationPolicyTO = new PropagationPolicyTO();
+        propagationPolicyTO.setDescription("propagation policy name");
+        propagationPolicyTO.setBackOffStrategy(BackOffStrategy.EXPONENTIAL);
+        propagationPolicyTO = createPolicy(PolicyType.PROPAGATION, propagationPolicyTO);
+        assertNotNull(propagationPolicyTO);
+        assertEquals(3, propagationPolicyTO.getMaxAttempts());
+        assertEquals(BackOffStrategy.EXPONENTIAL.getDefaultBackOffParams(), propagationPolicyTO.getBackOffParams());
+
         PullPolicyTO pullPolicyTO = createPolicy(PolicyType.PULL, buildPullPolicyTO());
         assertNotNull(pullPolicyTO);
         assertEquals("TestPullRule", pullPolicyTO.getCorrelationRules().get(AnyTypeKind.USER.name()));
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
index 41f0f93..f3b7e66 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.fit.core;
 
+import static org.awaitility.Awaitility.await;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
@@ -38,8 +39,10 @@ import java.util.Optional;
 import javax.ws.rs.core.Response;
 import org.apache.syncope.client.lib.batch.BatchRequest;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import javax.sql.DataSource;
 import javax.ws.rs.core.GenericType;
 import javax.xml.ws.WebServiceException;
 import org.apache.commons.lang3.SerializationUtils;
@@ -92,9 +95,17 @@ import org.identityconnectors.framework.common.objects.AttributeUtil;
 import org.identityconnectors.framework.common.objects.Name;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.BeforeAll;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
 
+@SpringJUnitConfig(locations = { "classpath:testJDBCEnv.xml" })
 public class PropagationTaskITCase extends AbstractTaskITCase {
 
+    @Autowired
+    private DataSource testDataSource;
+
     @BeforeAll
     public static void testItemTransformersSetup() {
         ImplementationTO dateToLong = null;
@@ -309,6 +320,41 @@ public class PropagationTaskITCase extends AbstractTaskITCase {
     }
 
     @Test
+    public void propagationPolicy() throws InterruptedException {
+        adminClient.nullPriorityAsync(anyObjectService, true);
+
+        JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource);
+        jdbcTemplate.execute("ALTER TABLE TESTPRINTER ADD COLUMN MAND_VALUE VARCHAR(1)");
+        jdbcTemplate.execute("UPDATE TESTPRINTER SET MAND_VALUE='C'");
+        jdbcTemplate.execute("ALTER TABLE TESTPRINTER ALTER COLUMN MAND_VALUE VARCHAR(1) NOT NULL");
+        try {
+            String entityKey = createAnyObject(AnyObjectITCase.getSampleTO("propagationPolicy")).getEntity().getKey();
+
+            Thread.sleep(1000);
+            jdbcTemplate.execute("ALTER TABLE TESTPRINTER DROP COLUMN MAND_VALUE");
+
+            PagedResult<PropagationTaskTO> propagations = await().atMost(MAX_WAIT_SECONDS, TimeUnit.SECONDS).until(
+                    () -> taskService.search(
+                            new TaskQuery.Builder(TaskType.PROPAGATION).resource(RESOURCE_NAME_DBSCRIPTED).
+                                    anyTypeKind(AnyTypeKind.ANY_OBJECT).entityKey(entityKey).build()),
+                    p -> p.getTotalCount() > 0);
+
+            propagations.getResult().get(0).getExecutions().stream().
+                    anyMatch(e -> ExecStatus.FAILURE.name().equals(e.getStatus()));
+            propagations.getResult().get(0).getExecutions().stream().
+                    anyMatch(e -> ExecStatus.SUCCESS.name().equals(e.getStatus()));
+        } finally {
+            adminClient.nullPriorityAsync(anyObjectService, false);
+
+            try {
+                jdbcTemplate.execute("ALTER TABLE TESTPRINTER DROP COLUMN MAND_VALUE");
+            } catch (DataAccessException e) {
+                // ignore
+            }
+        }
+    }
+
+    @Test
     public void issueSYNCOPE741() {
         for (int i = 0; i < 3; i++) {
             taskService.execute(new ExecuteQuery.Builder().
diff --git a/pom.xml b/pom.xml
index bb2cfb3..28f8d52 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1019,7 +1019,13 @@ under the License.
         <artifactId>spring-security-config</artifactId>
         <version>${spring-security.version}</version>
       </dependency>
-    
+
+      <dependency>
+        <groupId>org.springframework.retry</groupId>
+        <artifactId>spring-retry</artifactId>
+        <version>1.3.2</version>
+      </dependency>
+
       <dependency>
         <groupId>org.aspectj</groupId>
         <artifactId>aspectjweaver</artifactId>
diff --git a/src/main/asciidoc/reference-guide/concepts/policies.adoc b/src/main/asciidoc/reference-guide/concepts/policies.adoc
index 2421f56..661443d 100644
--- a/src/main/asciidoc/reference-guide/concepts/policies.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/policies.adoc
@@ -272,6 +272,20 @@ Before being able to configure the "Have I Been Pwned?" password rule as mention
 a `JAVA` `PASSWORD_RULE` <<implementations,implementation>> for the
 `org.apache.syncope.common.lib.policy.HaveIBeenPwnedPasswordRuleConf` class.
 
+[[policies-propagation]]
+==== Propagation
+
+Propagation policies are evaluated during the execution of <<tasks-propagation,propagation tasks>> and are meant to
+retry the configured operations in case of propagation failures.
+
+When defining a propagation policy, the following information must be provided:
+
+* max number of attempts
+* back-off strategy
+** `FIXED` - pauses for a fixed period of time before continuing
+** `EXPONENTIAL` - increases the back off period for each retry attempt in a given set up to a limit
+** `RANDOM` - chooses a random multiple of the interval that would come from a simple deterministic exponential
+
 [[policies-pull]]
 ==== Pull
 
diff --git a/src/main/asciidoc/reference-guide/concepts/tasks.adoc b/src/main/asciidoc/reference-guide/concepts/tasks.adoc
index 18c79a4..4127cdb 100644
--- a/src/main/asciidoc/reference-guide/concepts/tasks.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/tasks.adoc
@@ -60,8 +60,10 @@ ifeval::["{snapshotOrRelease}" == "snapshot"]
 https://github.com/apache/syncope/blob/2_1_X/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java[PriorityPropagationTaskExecutor^]
 endif::[]
 during the <<propagation,propagation>> process, and are permanently saved - for later re-execution or for examining
-the execution details - depending on the trace levels set on the related
-<<external-resource-details,external resource>>.
+the execution details - depending on the trace levels set on the related <<external-resource-details,external resource>>.
+
+Automatic retry in case of failure can be configured by mean of a <<policies-propagation,propagation policy>>, for the
+related external resource.
 ====
 
 [[tasks-pull]]