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

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


The following commit(s) were added to refs/heads/master by this push:
     new 126ffd3e2c [SYNCOPE-1908] Cleanup
126ffd3e2c is described below

commit 126ffd3e2cc874c26b97251d4c78342defd12a31
Author: Francesco Chicchiriccò <ilgro...@apache.org>
AuthorDate: Thu Sep 11 12:59:45 2025 +0200

    [SYNCOPE-1908] Cleanup
---
 .../apache/syncope/common/lib/EntityTOUtils.java   |  11 +-
 .../apache/syncope/common/lib/request/AnyCR.java   |  42 +++++++-
 .../syncope/common/lib/request/AnyObjectCR.java    |  35 -------
 .../syncope/common/lib/request/AnyObjectUR.java    |  27 +----
 .../apache/syncope/common/lib/request/AnyUR.java   |  31 +++++-
 .../apache/syncope/common/lib/request/GroupCR.java |  40 +------
 .../apache/syncope/common/lib/request/GroupUR.java |  14 +--
 .../apache/syncope/common/lib/request/UserCR.java  |  35 -------
 .../apache/syncope/common/lib/request/UserUR.java  |  27 +----
 .../apache/syncope/common/lib/to/AnyObjectTO.java  |  19 ----
 .../org/apache/syncope/common/lib/to/AnyTO.java    |  21 +++-
 .../org/apache/syncope/common/lib/to/GroupTO.java  |  21 +---
 .../org/apache/syncope/common/lib/to/UserTO.java   |  19 ----
 .../core/persistence/api/entity/AnyUtils.java      |   5 +
 .../core/persistence/api/entity/Groupable.java     |   3 +-
 .../core/persistence/api/entity/Relatable.java     |   3 +-
 .../api/entity/anyobject/AnyObject.java            |   4 +-
 .../core/persistence/api/entity/group/Group.java   |   3 +-
 .../core/persistence/api/entity/user/User.java     |   5 +-
 .../common/dao/AbstractAnyMatchDAO.java            |   8 +-
 .../persistence/common/entity/DefaultAnyUtils.java |  62 +++++++++++
 .../common/validation/AnyValidator.java            |   2 +-
 .../jpa/entity/AbstractGroupableRelatable.java     |   6 +-
 .../persistence/jpa/entity/AbstractRelatable.java  |   6 +-
 .../jpa/entity/anyobject/JPAAnyObject.java         |   2 +-
 .../persistence/jpa/entity/group/JPAGroup.java     |   3 +-
 .../core/persistence/jpa/entity/user/JPAUser.java  |   3 +-
 .../neo4j/entity/AbstractGroupableRelatable.java   |   7 +-
 .../neo4j/entity/AbstractRelatable.java            |   6 +-
 .../neo4j/entity/anyobject/Neo4jAnyObject.java     |   2 +-
 .../persistence/neo4j/entity/group/Neo4jGroup.java |   3 +-
 .../persistence/neo4j/entity/user/Neo4jUser.java   |   3 +-
 .../core/provisioning/api/DerAttrHandler.java      |   2 +-
 .../provisioning/java/DefaultDerAttrHandler.java   |   4 +-
 .../provisioning/java/DefaultMappingManager.java   |   8 +-
 .../core/provisioning/java/data/AnyDataBinder.java | 103 +++++++++++++++++-
 .../java/data/AnyObjectDataBinderImpl.java         | 116 +--------------------
 .../java/data/GroupDataBinderImpl.java             | 111 +-------------------
 .../provisioning/java/data/UserDataBinderImpl.java | 104 +-----------------
 .../elasticsearch/client/ElasticsearchUtils.java   |   2 +-
 .../ext/openfga/client/OpenFGAStoreManager.java    |   4 +-
 .../ext/opensearch/client/OpenSearchUtils.java     |   2 +-
 42 files changed, 320 insertions(+), 614 deletions(-)

diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/EntityTOUtils.java
 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/EntityTOUtils.java
index 9b7d37259d..7f10da1df0 100644
--- 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/EntityTOUtils.java
+++ 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/EntityTOUtils.java
@@ -68,19 +68,17 @@ public final class EntityTOUtils {
         anyCR.getAuxClasses().addAll(anyTO.getAuxClasses());
         anyCR.getPlainAttrs().addAll(anyTO.getPlainAttrs());
         anyCR.getResources().addAll(anyTO.getResources());
+        anyCR.getRelationships().addAll(anyTO.getRelationships());
 
         if (anyCR instanceof final UserCR userCR && anyTO instanceof final 
UserTO userTO) {
-
             userCR.setUsername(userTO.getUsername());
             userCR.setPassword(userTO.getPassword());
             userCR.setSecurityQuestion(userTO.getSecurityQuestion());
             userCR.setSecurityAnswer(userTO.getSecurityAnswer());
             userCR.setMustChangePassword(userTO.isMustChangePassword());
-            userCR.getRelationships().addAll(userTO.getRelationships());
             userCR.getMemberships().addAll(userTO.getMemberships());
             userCR.getRoles().addAll(userTO.getRoles());
         } else if (anyCR instanceof final GroupCR groupCR && anyTO instanceof 
final GroupTO groupTO) {
-
             groupCR.setName(groupTO.getName());
             groupCR.setUserOwner(groupTO.getUserOwner());
             groupCR.setGroupOwner(groupTO.getGroupOwner());
@@ -88,10 +86,8 @@ public final class EntityTOUtils {
             
groupCR.getADynMembershipConds().putAll(groupTO.getADynMembershipConds());
             groupCR.getTypeExtensions().addAll(groupTO.getTypeExtensions());
         } else if (anyCR instanceof final AnyObjectCR anyObjectCR && anyTO 
instanceof final AnyObjectTO anyObjectTO) {
-
             anyObjectCR.setType(anyObjectTO.getType());
             anyObjectCR.setName(anyObjectTO.getName());
-            
anyObjectCR.getRelationships().addAll(anyObjectTO.getRelationships());
             anyObjectCR.getMemberships().addAll(anyObjectTO.getMemberships());
         }
     }
@@ -101,15 +97,14 @@ public final class EntityTOUtils {
         anyTO.getAuxClasses().addAll(anyCR.getAuxClasses());
         anyTO.getPlainAttrs().addAll(anyCR.getPlainAttrs());
         anyTO.getResources().addAll(anyCR.getResources());
+        anyTO.getRelationships().addAll(anyCR.getRelationships());
 
         if (anyTO instanceof final UserTO userTO && anyCR instanceof final 
UserCR userCR) {
-
             userTO.setUsername(userCR.getUsername());
             userTO.setPassword(userCR.getPassword());
             userTO.setSecurityQuestion(userCR.getSecurityQuestion());
             userTO.setSecurityAnswer(userCR.getSecurityAnswer());
             userTO.setMustChangePassword(userCR.isMustChangePassword());
-            userTO.getRelationships().addAll(userCR.getRelationships());
             userTO.getMemberships().addAll(userCR.getMemberships());
             userTO.getRoles().addAll(userCR.getRoles());
         } else if (anyTO instanceof final GroupTO groupTO && anyCR instanceof 
final GroupCR groupCR) {
@@ -121,10 +116,8 @@ public final class EntityTOUtils {
             
groupTO.getADynMembershipConds().putAll(groupCR.getADynMembershipConds());
             groupTO.getTypeExtensions().addAll(groupCR.getTypeExtensions());
         } else if (anyTO instanceof final AnyObjectTO anyObjectTO && anyCR 
instanceof final AnyObjectCR anyObjectCR) {
-
             anyObjectTO.setType(anyObjectCR.getType());
             anyObjectTO.setName(anyObjectCR.getName());
-            
anyObjectTO.getRelationships().addAll(anyObjectCR.getRelationships());
             anyObjectTO.getMemberships().addAll(anyObjectCR.getMemberships());
         }
     }
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/AnyCR.java
 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/AnyCR.java
index f7d67b90ec..9c2bb11e09 100644
--- 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/AnyCR.java
+++ 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/AnyCR.java
@@ -25,6 +25,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
 import 
com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
@@ -35,11 +36,13 @@ import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.syncope.common.lib.Attr;
 import org.apache.syncope.common.lib.BaseBean;
 import org.apache.syncope.common.lib.RealmMember;
+import org.apache.syncope.common.lib.to.RelatableTO;
+import org.apache.syncope.common.lib.to.RelationshipTO;
 
 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = 
JsonTypeInfo.As.EXISTING_PROPERTY, property = "_class")
 @JsonPropertyOrder(value = { "_class" })
 @Schema(subTypes = { UserCR.class, GroupCR.class, AnyObjectCR.class }, 
discriminatorProperty = "_class")
-public abstract class AnyCR implements BaseBean, RealmMember {
+public abstract class AnyCR implements BaseBean, RealmMember, RelatableTO {
 
     private static final long serialVersionUID = -1180587903919947455L;
 
@@ -114,6 +117,24 @@ public abstract class AnyCR implements BaseBean, 
RealmMember {
             return (B) this;
         }
 
+        @SuppressWarnings("unchecked")
+        public B relationship(final RelationshipTO relationship) {
+            getInstance().getRelationships().add(relationship);
+            return (B) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        public B relationships(final RelationshipTO... relationships) {
+            getInstance().getRelationships().addAll(List.of(relationships));
+            return (B) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        public B relationships(final Collection<RelationshipTO> relationships) 
{
+            getInstance().getRelationships().addAll(relationships);
+            return (B) this;
+        }
+
         public R build() {
             return getInstance();
         }
@@ -131,6 +152,8 @@ public abstract class AnyCR implements BaseBean, 
RealmMember {
 
     private final Set<String> resources = new HashSet<>();
 
+    private final List<RelationshipTO> relationships = new ArrayList<>();
+
     @Schema(name = "_class", requiredMode = Schema.RequiredMode.REQUIRED)
     public abstract String getDiscriminator();
 
@@ -186,6 +209,21 @@ public abstract class AnyCR implements BaseBean, 
RealmMember {
         return resources;
     }
 
+    @JsonIgnore
+    @Override
+    public Optional<RelationshipTO> getRelationship(final String type, final 
String otherKey) {
+        return relationships.stream().filter(
+                relationship -> type.equals(relationship.getType()) && 
otherKey.equals(relationship.getOtherEndKey())).
+                findFirst();
+    }
+
+    @JacksonXmlElementWrapper(localName = "relationships")
+    @JacksonXmlProperty(localName = "relationship")
+    @Override
+    public List<RelationshipTO> getRelationships() {
+        return relationships;
+    }
+
     @Override
     public int hashCode() {
         return new HashCodeBuilder().
@@ -194,6 +232,7 @@ public abstract class AnyCR implements BaseBean, 
RealmMember {
                 append(auxClasses).
                 append(plainAttrs).
                 append(resources).
+                append(relationships).
                 build();
     }
 
@@ -215,6 +254,7 @@ public abstract class AnyCR implements BaseBean, 
RealmMember {
                 append(auxClasses, other.auxClasses).
                 append(plainAttrs, other.plainAttrs).
                 append(resources, other.resources).
+                append(relationships, other.relationships).
                 build();
     }
 }
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/AnyObjectCR.java
 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/AnyObjectCR.java
index 701ed11b71..7925d4be76 100644
--- 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/AnyObjectCR.java
+++ 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/AnyObjectCR.java
@@ -32,7 +32,6 @@ import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.syncope.common.lib.to.GroupableRelatableTO;
 import org.apache.syncope.common.lib.to.MembershipTO;
-import org.apache.syncope.common.lib.to.RelationshipTO;
 
 @JsonPropertyOrder(value = { "_class", "name" })
 @Schema(allOf = { AnyCR.class })
@@ -53,21 +52,6 @@ public class AnyObjectCR extends AnyCR implements 
GroupableRelatableTO {
             getInstance().setName(name);
         }
 
-        public Builder relationship(final RelationshipTO relationship) {
-            getInstance().getRelationships().add(relationship);
-            return this;
-        }
-
-        public Builder relationships(final RelationshipTO... relationships) {
-            getInstance().getRelationships().addAll(List.of(relationships));
-            return this;
-        }
-
-        public Builder relationships(final Collection<RelationshipTO> 
relationships) {
-            getInstance().getRelationships().addAll(relationships);
-            return this;
-        }
-
         public Builder membership(final MembershipTO membership) {
             getInstance().getMemberships().add(membership);
             return this;
@@ -88,8 +72,6 @@ public class AnyObjectCR extends AnyCR implements 
GroupableRelatableTO {
 
     private String name;
 
-    private final List<RelationshipTO> relationships = new ArrayList<>();
-
     private final List<MembershipTO> memberships = new ArrayList<>();
 
     @JacksonXmlProperty(localName = "_class", isAttribute = true)
@@ -119,21 +101,6 @@ public class AnyObjectCR extends AnyCR implements 
GroupableRelatableTO {
         this.name = name;
     }
 
-    @JsonIgnore
-    @Override
-    public Optional<RelationshipTO> getRelationship(final String type, final 
String otherKey) {
-        return relationships.stream().filter(
-                relationship -> type.equals(relationship.getType()) && 
otherKey.equals(relationship.getOtherEndKey())).
-                findFirst();
-    }
-
-    @JacksonXmlElementWrapper(localName = "relationships")
-    @JacksonXmlProperty(localName = "relationship")
-    @Override
-    public List<RelationshipTO> getRelationships() {
-        return relationships;
-    }
-
     @JsonIgnore
     @Override
     public Optional<MembershipTO> getMembership(final String groupKey) {
@@ -158,7 +125,6 @@ public class AnyObjectCR extends AnyCR implements 
GroupableRelatableTO {
         return new HashCodeBuilder().
                 appendSuper(super.hashCode()).
                 append(name).
-                append(relationships).
                 append(memberships).
                 build();
     }
@@ -178,7 +144,6 @@ public class AnyObjectCR extends AnyCR implements 
GroupableRelatableTO {
         return new EqualsBuilder().
                 appendSuper(super.equals(obj)).
                 append(name, other.name).
-                append(relationships, other.relationships).
                 append(memberships, other.memberships).
                 build();
     }
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/AnyObjectUR.java
 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/AnyObjectUR.java
index 6064c85847..156047c642 100644
--- 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/AnyObjectUR.java
+++ 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/AnyObjectUR.java
@@ -50,21 +50,6 @@ public class AnyObjectUR extends AnyUR {
             return this;
         }
 
-        public Builder relationship(final RelationshipUR relationship) {
-            getInstance().getRelationships().add(relationship);
-            return this;
-        }
-
-        public Builder relationships(final RelationshipUR... relationships) {
-            getInstance().getRelationships().addAll(List.of(relationships));
-            return this;
-        }
-
-        public Builder relationships(final Collection<RelationshipUR> 
relationships) {
-            getInstance().getRelationships().addAll(relationships);
-            return this;
-        }
-
         public Builder membership(final MembershipUR membership) {
             getInstance().getMemberships().add(membership);
             return this;
@@ -83,8 +68,6 @@ public class AnyObjectUR extends AnyUR {
 
     private StringReplacePatchItem name;
 
-    private final Set<RelationshipUR> relationships = new HashSet<>();
-
     private final Set<MembershipUR> memberships = new HashSet<>();
 
     @JacksonXmlProperty(localName = "_class", isAttribute = true)
@@ -104,12 +87,6 @@ public class AnyObjectUR extends AnyUR {
         this.name = name;
     }
 
-    @JacksonXmlElementWrapper(localName = "relationships")
-    @JacksonXmlProperty(localName = "relationship")
-    public Set<RelationshipUR> getRelationships() {
-        return relationships;
-    }
-
     @JacksonXmlElementWrapper(localName = "memberships")
     @JacksonXmlProperty(localName = "membership")
     public Set<MembershipUR> getMemberships() {
@@ -118,7 +95,7 @@ public class AnyObjectUR extends AnyUR {
 
     @Override
     public boolean isEmpty() {
-        return super.isEmpty() && name == null && relationships.isEmpty() && 
memberships.isEmpty();
+        return super.isEmpty() && name == null && memberships.isEmpty();
     }
 
     @Override
@@ -126,7 +103,6 @@ public class AnyObjectUR extends AnyUR {
         return new HashCodeBuilder().
                 appendSuper(super.hashCode()).
                 append(name).
-                append(relationships).
                 append(memberships).
                 build();
     }
@@ -146,7 +122,6 @@ public class AnyObjectUR extends AnyUR {
         return new EqualsBuilder().
                 appendSuper(super.equals(obj)).
                 append(name, other.name).
-                append(relationships, other.relationships).
                 append(memberships, other.memberships).
                 build();
     }
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/AnyUR.java
 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/AnyUR.java
index 7e2f8cbb54..e3f931077d 100644
--- 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/AnyUR.java
+++ 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/AnyUR.java
@@ -118,6 +118,24 @@ public abstract class AnyUR implements BaseBean {
             return (B) this;
         }
 
+        @SuppressWarnings("unchecked")
+        public B relationship(final RelationshipUR relationship) {
+            getInstance().getRelationships().add(relationship);
+            return (B) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        public B relationships(final RelationshipUR... relationships) {
+            getInstance().getRelationships().addAll(List.of(relationships));
+            return (B) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        public B relationships(final Collection<RelationshipUR> relationships) 
{
+            getInstance().getRelationships().addAll(relationships);
+            return (B) this;
+        }
+
         public R build() {
             return getInstance();
         }
@@ -137,6 +155,8 @@ public abstract class AnyUR implements BaseBean {
 
     private final Set<StringPatchItem> resources = new HashSet<>();
 
+    private final Set<RelationshipUR> relationships = new HashSet<>();
+
     @Schema(name = "_class", requiredMode = Schema.RequiredMode.REQUIRED)
     public abstract String getDiscriminator();
 
@@ -180,6 +200,12 @@ public abstract class AnyUR implements BaseBean {
         return resources;
     }
 
+    @JacksonXmlElementWrapper(localName = "relationships")
+    @JacksonXmlProperty(localName = "relationship")
+    public Set<RelationshipUR> getRelationships() {
+        return relationships;
+    }
+
     /**
      * @return true if no actual changes are defined
      */
@@ -188,7 +214,8 @@ public abstract class AnyUR implements BaseBean {
         return realm == null
                 && auxClasses.isEmpty()
                 && plainAttrs.isEmpty()
-                && resources.isEmpty();
+                && resources.isEmpty()
+                && relationships.isEmpty();
     }
 
     @Override
@@ -200,6 +227,7 @@ public abstract class AnyUR implements BaseBean {
                 append(auxClasses).
                 append(plainAttrs).
                 append(resources).
+                append(relationships).
                 build();
     }
 
@@ -222,6 +250,7 @@ public abstract class AnyUR implements BaseBean {
                 append(auxClasses, other.auxClasses).
                 append(plainAttrs, other.plainAttrs).
                 append(resources, other.resources).
+                append(relationships, other.relationships).
                 build();
     }
 }
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/GroupCR.java
 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/GroupCR.java
index 9b5ddac6bb..bcf529504d 100644
--- 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/GroupCR.java
+++ 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/GroupCR.java
@@ -18,7 +18,6 @@
  */
 package org.apache.syncope.common.lib.request;
 
-import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonPropertyOrder;
 import 
com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
@@ -29,16 +28,13 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
-import org.apache.syncope.common.lib.to.RelatableTO;
-import org.apache.syncope.common.lib.to.RelationshipTO;
 import org.apache.syncope.common.lib.to.TypeExtensionTO;
 
 @JsonPropertyOrder(value = { "_class", "name" })
 @Schema(allOf = { AnyCR.class })
-public class GroupCR extends AnyCR implements RelatableTO {
+public class GroupCR extends AnyCR {
 
     private static final long serialVersionUID = -4559772531167385473L;
 
@@ -88,21 +84,6 @@ public class GroupCR extends AnyCR implements RelatableTO {
             getInstance().getTypeExtensions().addAll(typeExtensions);
             return this;
         }
-
-        public Builder relationship(final RelationshipTO relationship) {
-            getInstance().getRelationships().add(relationship);
-            return this;
-        }
-
-        public Builder relationships(final RelationshipTO... relationships) {
-            getInstance().getRelationships().addAll(List.of(relationships));
-            return this;
-        }
-
-        public Builder relationships(final Collection<RelationshipTO> 
relationships) {
-            getInstance().getRelationships().addAll(relationships);
-            return this;
-        }
     }
 
     private String name;
@@ -117,8 +98,6 @@ public class GroupCR extends AnyCR implements RelatableTO {
 
     private final List<TypeExtensionTO> typeExtensions = new ArrayList<>();
 
-    private final List<RelationshipTO> relationships = new ArrayList<>();
-
     @JacksonXmlProperty(localName = "_class", isAttribute = true)
     @JsonProperty("_class")
     @Schema(name = "_class", requiredMode = Schema.RequiredMode.REQUIRED,
@@ -171,21 +150,6 @@ public class GroupCR extends AnyCR implements RelatableTO {
         return typeExtensions;
     }
 
-    @JsonIgnore
-    @Override
-    public Optional<RelationshipTO> getRelationship(final String type, final 
String otherKey) {
-        return relationships.stream().filter(
-                relationship -> type.equals(relationship.getType()) && 
otherKey.equals(relationship.getOtherEndKey())).
-                findFirst();
-    }
-
-    @JacksonXmlElementWrapper(localName = "relationships")
-    @JacksonXmlProperty(localName = "relationship")
-    @Override
-    public List<RelationshipTO> getRelationships() {
-        return relationships;
-    }
-
     @Override
     public int hashCode() {
         return new HashCodeBuilder().
@@ -196,7 +160,6 @@ public class GroupCR extends AnyCR implements RelatableTO {
                 append(udynMembershipCond).
                 append(adynMembershipConds).
                 append(typeExtensions).
-                append(relationships).
                 build();
     }
 
@@ -220,7 +183,6 @@ public class GroupCR extends AnyCR implements RelatableTO {
                 append(udynMembershipCond, other.udynMembershipCond).
                 append(adynMembershipConds, other.adynMembershipConds).
                 append(typeExtensions, other.typeExtensions).
-                append(relationships, other.relationships).
                 build();
     }
 }
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/GroupUR.java
 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/GroupUR.java
index e932c5143e..d28b933f30 100644
--- 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/GroupUR.java
+++ 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/GroupUR.java
@@ -26,11 +26,9 @@ import io.swagger.v3.oas.annotations.media.Schema;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.Set;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.syncope.common.lib.to.TypeExtensionTO;
@@ -109,8 +107,6 @@ public class GroupUR extends AnyUR {
 
     private final List<TypeExtensionTO> typeExtensions = new ArrayList<>();
 
-    private final Set<RelationshipUR> relationships = new HashSet<>();
-
     @JacksonXmlProperty(localName = "_class", isAttribute = true)
     @JsonProperty("_class")
     @Schema(name = "_class", requiredMode = Schema.RequiredMode.REQUIRED,
@@ -168,17 +164,11 @@ public class GroupUR extends AnyUR {
         return typeExtensions;
     }
 
-    @JacksonXmlElementWrapper(localName = "relationships")
-    @JacksonXmlProperty(localName = "relationship")
-    public Set<RelationshipUR> getRelationships() {
-        return relationships;
-    }
-
     @Override
     public boolean isEmpty() {
         return super.isEmpty()
                 && name == null && userOwner == null && groupOwner == null && 
udynMembershipCond == null
-                && adynMembershipConds.isEmpty() && typeExtensions.isEmpty() 
&& relationships.isEmpty();
+                && adynMembershipConds.isEmpty() && typeExtensions.isEmpty();
     }
 
     @Override
@@ -191,7 +181,6 @@ public class GroupUR extends AnyUR {
                 append(udynMembershipCond).
                 append(adynMembershipConds).
                 append(typeExtensions).
-                append(relationships).
                 build();
     }
 
@@ -215,7 +204,6 @@ public class GroupUR extends AnyUR {
                 append(udynMembershipCond, other.udynMembershipCond).
                 append(adynMembershipConds, other.adynMembershipConds).
                 append(typeExtensions, other.typeExtensions).
-                append(relationships, other.relationships).
                 build();
     }
 }
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/UserCR.java
 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/UserCR.java
index fb141c16c6..2564fb66b5 100644
--- 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/UserCR.java
+++ 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/UserCR.java
@@ -35,7 +35,6 @@ import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.syncope.common.lib.to.GroupableRelatableTO;
 import org.apache.syncope.common.lib.to.LinkedAccountTO;
 import org.apache.syncope.common.lib.to.MembershipTO;
-import org.apache.syncope.common.lib.to.RelationshipTO;
 
 @JsonPropertyOrder(value = { "_class", "username" })
 @Schema(allOf = { AnyCR.class })
@@ -80,21 +79,6 @@ public class UserCR extends AnyCR implements 
GroupableRelatableTO {
             return this;
         }
 
-        public Builder relationship(final RelationshipTO relationship) {
-            getInstance().getRelationships().add(relationship);
-            return this;
-        }
-
-        public Builder relationships(final RelationshipTO... relationships) {
-            getInstance().getRelationships().addAll(List.of(relationships));
-            return this;
-        }
-
-        public Builder relationships(final Collection<RelationshipTO> 
relationships) {
-            getInstance().getRelationships().addAll(relationships);
-            return this;
-        }
-
         public Builder membership(final MembershipTO membership) {
             getInstance().getMemberships().add(membership);
             return this;
@@ -153,8 +137,6 @@ public class UserCR extends AnyCR implements 
GroupableRelatableTO {
 
     private boolean mustChangePassword;
 
-    private final List<RelationshipTO> relationships = new ArrayList<>();
-
     private final List<MembershipTO> memberships = new ArrayList<>();
 
     private final Set<String> roles = new HashSet<>();
@@ -219,21 +201,6 @@ public class UserCR extends AnyCR implements 
GroupableRelatableTO {
         this.mustChangePassword = mustChangePassword;
     }
 
-    @JsonIgnore
-    @Override
-    public Optional<RelationshipTO> getRelationship(final String type, final 
String otherKey) {
-        return relationships.stream().filter(
-                relationship -> type.equals(relationship.getType()) && 
otherKey.equals(relationship.getOtherEndKey())).
-                findFirst();
-    }
-
-    @JacksonXmlElementWrapper(localName = "relationships")
-    @JacksonXmlProperty(localName = "relationship")
-    @Override
-    public List<RelationshipTO> getRelationships() {
-        return relationships;
-    }
-
     @JsonIgnore
     @Override
     public Optional<MembershipTO> getMembership(final String groupKey) {
@@ -274,7 +241,6 @@ public class UserCR extends AnyCR implements 
GroupableRelatableTO {
                 append(securityQuestion).
                 append(securityAnswer).
                 append(mustChangePassword).
-                append(relationships).
                 append(memberships).
                 append(linkedAccounts).
                 build();
@@ -299,7 +265,6 @@ public class UserCR extends AnyCR implements 
GroupableRelatableTO {
                 append(securityQuestion, other.securityQuestion).
                 append(securityAnswer, other.securityAnswer).
                 append(mustChangePassword, other.mustChangePassword).
-                append(relationships, other.relationships).
                 append(memberships, other.memberships).
                 append(linkedAccounts, other.linkedAccounts).
                 build();
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/UserUR.java
 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/UserUR.java
index c0873e0b92..193495f103 100644
--- 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/UserUR.java
+++ 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/request/UserUR.java
@@ -72,21 +72,6 @@ public class UserUR extends AnyUR {
             return this;
         }
 
-        public Builder relationship(final RelationshipUR relationship) {
-            getInstance().getRelationships().add(relationship);
-            return this;
-        }
-
-        public Builder relationships(final RelationshipUR... relationships) {
-            getInstance().getRelationships().addAll(List.of(relationships));
-            return this;
-        }
-
-        public Builder relationships(final Collection<RelationshipUR> 
relationships) {
-            getInstance().getRelationships().addAll(relationships);
-            return this;
-        }
-
         public Builder membership(final MembershipUR membership) {
             getInstance().getMemberships().add(membership);
             return this;
@@ -128,8 +113,6 @@ public class UserUR extends AnyUR {
 
     private BooleanReplacePatchItem mustChangePassword;
 
-    private final Set<RelationshipUR> relationships = new HashSet<>();
-
     private final Set<MembershipUR> memberships = new HashSet<>();
 
     private final Set<StringPatchItem> roles = new HashSet<>();
@@ -185,12 +168,6 @@ public class UserUR extends AnyUR {
         this.mustChangePassword = mustChangePassword;
     }
 
-    @JacksonXmlElementWrapper(localName = "relationships")
-    @JacksonXmlProperty(localName = "relationship")
-    public Set<RelationshipUR> getRelationships() {
-        return relationships;
-    }
-
     @JacksonXmlElementWrapper(localName = "memberships")
     @JacksonXmlProperty(localName = "membership")
     public Set<MembershipUR> getMemberships() {
@@ -213,7 +190,7 @@ public class UserUR extends AnyUR {
     protected boolean isEmptyNotConsideringPassword() {
         return super.isEmpty()
                 && username == null && securityQuestion == null && 
securityAnswer == null
-                && mustChangePassword == null && relationships.isEmpty() && 
memberships.isEmpty() && roles.isEmpty()
+                && mustChangePassword == null && memberships.isEmpty() && 
roles.isEmpty()
                 && linkedAccounts.isEmpty();
     }
 
@@ -236,7 +213,6 @@ public class UserUR extends AnyUR {
                 append(securityQuestion).
                 append(securityAnswer).
                 append(mustChangePassword).
-                append(relationships).
                 append(memberships).
                 append(roles).
                 append(linkedAccounts).
@@ -261,7 +237,6 @@ public class UserUR extends AnyUR {
                 append(securityQuestion, other.securityQuestion).
                 append(securityAnswer, other.securityAnswer).
                 append(mustChangePassword, other.mustChangePassword).
-                append(relationships, other.relationships).
                 append(memberships, other.memberships).
                 append(roles, other.roles).
                 append(linkedAccounts, other.linkedAccounts).
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AnyObjectTO.java
 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AnyObjectTO.java
index 4975086601..39d283ebb4 100644
--- 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AnyObjectTO.java
+++ 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AnyObjectTO.java
@@ -36,8 +36,6 @@ public class AnyObjectTO extends AnyTO implements 
GroupableRelatableTO {
 
     private String name;
 
-    private final List<RelationshipTO> relationships = new ArrayList<>();
-
     private final List<MembershipTO> memberships = new ArrayList<>();
 
     private final List<MembershipTO> dynMemberships = new ArrayList<>();
@@ -59,21 +57,6 @@ public class AnyObjectTO extends AnyTO implements 
GroupableRelatableTO {
         this.name = name;
     }
 
-    @JsonIgnore
-    @Override
-    public Optional<RelationshipTO> getRelationship(final String type, final 
String otherKey) {
-        return relationships.stream().filter(
-                relationship -> type.equals(relationship.getType()) && 
otherKey.equals(relationship.getOtherEndKey())).
-                findFirst();
-    }
-
-    @JacksonXmlElementWrapper(localName = "relationships")
-    @JacksonXmlProperty(localName = "relationship")
-    @Override
-    public List<RelationshipTO> getRelationships() {
-        return relationships;
-    }
-
     @JsonIgnore
     @Override
     public Optional<MembershipTO> getMembership(final String groupKey) {
@@ -99,7 +82,6 @@ public class AnyObjectTO extends AnyTO implements 
GroupableRelatableTO {
         return new HashCodeBuilder().
                 appendSuper(super.hashCode()).
                 append(name).
-                append(relationships).
                 append(memberships).
                 append(dynMemberships).
                 build();
@@ -120,7 +102,6 @@ public class AnyObjectTO extends AnyTO implements 
GroupableRelatableTO {
         return new EqualsBuilder().
                 appendSuper(super.equals(obj)).
                 append(name, other.name).
-                append(relationships, other.relationships).
                 append(memberships, other.memberships).
                 append(dynMemberships, other.dynMemberships).
                 build();
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AnyTO.java 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AnyTO.java
index 09665bc5ef..32851b2958 100644
--- 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AnyTO.java
+++ 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/AnyTO.java
@@ -40,7 +40,7 @@ import org.apache.syncope.common.lib.RealmMember;
 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = 
JsonTypeInfo.As.EXISTING_PROPERTY, property = "_class")
 @JsonPropertyOrder(value = { "_class", "key", "type", "realm", "username", 
"name" })
 @Schema(subTypes = { UserTO.class, GroupTO.class, AnyObjectTO.class }, 
discriminatorProperty = "_class")
-public abstract class AnyTO implements EntityTO, RealmMember {
+public abstract class AnyTO implements EntityTO, RealmMember, RelatableTO {
 
     private static final long serialVersionUID = -754311920679872084L;
 
@@ -90,6 +90,8 @@ public abstract class AnyTO implements EntityTO, RealmMember {
 
     private final Set<String> resources = new TreeSet<>();
 
+    private final List<RelationshipTO> relationships = new ArrayList<>();
+
     @Schema(name = "_class", requiredMode = Schema.RequiredMode.REQUIRED)
     public abstract String getDiscriminator();
 
@@ -234,6 +236,21 @@ public abstract class AnyTO implements EntityTO, 
RealmMember {
         return resources;
     }
 
+    @JsonIgnore
+    @Override
+    public Optional<RelationshipTO> getRelationship(final String type, final 
String otherKey) {
+        return relationships.stream().filter(
+                relationship -> type.equals(relationship.getType()) && 
otherKey.equals(relationship.getOtherEndKey())).
+                findFirst();
+    }
+
+    @JacksonXmlElementWrapper(localName = "relationships")
+    @JacksonXmlProperty(localName = "relationship")
+    @Override
+    public List<RelationshipTO> getRelationships() {
+        return relationships;
+    }
+
     @Override
     public int hashCode() {
         return new HashCodeBuilder().
@@ -252,6 +269,7 @@ public abstract class AnyTO implements EntityTO, 
RealmMember {
                 append(plainAttrs).
                 append(derAttrs).
                 append(resources).
+                append(relationships).
                 build();
     }
 
@@ -283,6 +301,7 @@ public abstract class AnyTO implements EntityTO, 
RealmMember {
                 append(plainAttrs, other.plainAttrs).
                 append(derAttrs, other.derAttrs).
                 append(resources, other.resources).
+                append(relationships, other.relationships).
                 build();
     }
 }
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/GroupTO.java 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/GroupTO.java
index aa33846ae4..575bf45429 100644
--- 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/GroupTO.java
+++ 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/GroupTO.java
@@ -33,7 +33,7 @@ import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 
 @Schema(allOf = { AnyTO.class })
-public class GroupTO extends AnyTO implements RelatableTO {
+public class GroupTO extends AnyTO {
 
     private static final long serialVersionUID = -7785920258290147542L;
 
@@ -57,8 +57,6 @@ public class GroupTO extends AnyTO implements RelatableTO {
 
     private final List<TypeExtensionTO> typeExtensions = new ArrayList<>();
 
-    private final List<RelationshipTO> relationships = new ArrayList<>();
-
     @JacksonXmlProperty(localName = "_class", isAttribute = true)
     @JsonProperty("_class")
     @Schema(name = "_class", requiredMode = Schema.RequiredMode.REQUIRED,
@@ -159,21 +157,6 @@ public class GroupTO extends AnyTO implements RelatableTO {
         return typeExtensions;
     }
 
-    @JsonIgnore
-    @Override
-    public Optional<RelationshipTO> getRelationship(final String type, final 
String otherKey) {
-        return relationships.stream().filter(
-                relationship -> type.equals(relationship.getType()) && 
otherKey.equals(relationship.getOtherEndKey())).
-                findFirst();
-    }
-
-    @JacksonXmlElementWrapper(localName = "relationships")
-    @JacksonXmlProperty(localName = "relationship")
-    @Override
-    public List<RelationshipTO> getRelationships() {
-        return relationships;
-    }
-
     @Override
     public int hashCode() {
         return new HashCodeBuilder().
@@ -184,7 +167,6 @@ public class GroupTO extends AnyTO implements RelatableTO {
                 append(udynMembershipCond).
                 append(adynMembershipConds).
                 append(typeExtensions).
-                append(relationships).
                 build();
     }
 
@@ -208,7 +190,6 @@ public class GroupTO extends AnyTO implements RelatableTO {
                 append(udynMembershipCond, other.udynMembershipCond).
                 append(adynMembershipConds, other.adynMembershipConds).
                 append(typeExtensions, other.typeExtensions).
-                append(relationships, other.relationships).
                 build();
     }
 }
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/UserTO.java 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/UserTO.java
index eeedee8b4f..49460a47d8 100644
--- 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/UserTO.java
+++ 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/UserTO.java
@@ -58,8 +58,6 @@ public class UserTO extends AnyTO implements 
GroupableRelatableTO {
 
     private boolean mustChangePassword;
 
-    private final List<RelationshipTO> relationships = new ArrayList<>();
-
     private final List<MembershipTO> memberships = new ArrayList<>();
 
     private final List<MembershipTO> dynMemberships = new ArrayList<>();
@@ -182,21 +180,6 @@ public class UserTO extends AnyTO implements 
GroupableRelatableTO {
         this.mustChangePassword = mustChangePassword;
     }
 
-    @JsonIgnore
-    @Override
-    public Optional<RelationshipTO> getRelationship(final String type, final 
String otherKey) {
-        return relationships.stream().filter(
-                relationship -> type.equals(relationship.getType()) && 
otherKey.equals(relationship.getOtherEndKey())).
-                findFirst();
-    }
-
-    @JacksonXmlElementWrapper(localName = "relationships")
-    @JacksonXmlProperty(localName = "relationship")
-    @Override
-    public List<RelationshipTO> getRelationships() {
-        return relationships;
-    }
-
     @JsonIgnore
     @Override
     public Optional<MembershipTO> getMembership(final String groupKey) {
@@ -265,7 +248,6 @@ public class UserTO extends AnyTO implements 
GroupableRelatableTO {
                 append(securityAnswer).
                 append(suspended).
                 append(mustChangePassword).
-                append(relationships).
                 append(memberships).
                 append(dynMemberships).
                 append(linkedAccounts).
@@ -300,7 +282,6 @@ public class UserTO extends AnyTO implements 
GroupableRelatableTO {
                 append(securityAnswer, other.securityAnswer).
                 append(suspended, other.suspended).
                 append(mustChangePassword, other.mustChangePassword).
-                append(relationships, other.relationships).
                 append(memberships, other.memberships).
                 append(dynMemberships, other.dynMemberships).
                 append(linkedAccounts, other.linkedAccounts).
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
index fd4b715b30..48350b0e39 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
@@ -27,6 +27,7 @@ import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import 
org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyDAO;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 
 public interface AnyUtils {
 
@@ -49,4 +50,8 @@ public interface AnyUtils {
     void addAttr(PlainAttrValidationManager validator, String key, PlainSchema 
schema, String value);
 
     void removeAttr(String key, PlainSchema schema);
+
+    void addRelationship(Relatable<?, ?> relatable, RelationshipType 
relationshipType, AnyObject otherEnd);
+
+    void removeRelationship(Relatable<?, ?> relatable, RelationshipType 
relationshipType, String otherEndKey);
 }
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Groupable.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Groupable.java
index adf8004e21..c8f1ca01ba 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Groupable.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Groupable.java
@@ -21,8 +21,9 @@ package org.apache.syncope.core.persistence.api.entity;
 import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 
-public interface Groupable<L extends Any, M extends Membership<L>, R extends 
Any, REL extends Relationship<L, R>>
+public interface Groupable<L extends Any, M extends Membership<L>, REL extends 
Relationship<L, AnyObject>>
         extends Any {
 
     /**
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Relatable.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Relatable.java
index c53e66a188..d1d352db37 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Relatable.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Relatable.java
@@ -21,8 +21,9 @@ package org.apache.syncope.core.persistence.api.entity;
 import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 
-public interface Relatable<L extends Any, R extends Any, REL extends 
Relationship<L, R>> extends Any {
+public interface Relatable<L extends Any, REL extends Relationship<L, 
AnyObject>> extends Any {
 
     boolean add(REL relationship);
 
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/anyobject/AnyObject.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/anyobject/AnyObject.java
index 44755ef89f..15ac74d51e 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/anyobject/AnyObject.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/anyobject/AnyObject.java
@@ -22,8 +22,8 @@ import 
org.apache.syncope.core.persistence.api.entity.Groupable;
 import org.apache.syncope.core.persistence.api.entity.Relatable;
 
 public interface AnyObject extends
-        Groupable<AnyObject, AMembership, AnyObject, ARelationship>,
-        Relatable<AnyObject, AnyObject, ARelationship> {
+        Groupable<AnyObject, AMembership, ARelationship>,
+        Relatable<AnyObject, ARelationship> {
 
     String getName();
 
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/group/Group.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/group/Group.java
index 993d190f5c..8911bbd51e 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/group/Group.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/group/Group.java
@@ -23,11 +23,10 @@ import java.util.Optional;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.Relatable;
 import 
org.apache.syncope.core.persistence.api.entity.anyobject.ADynGroupMembership;
-import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.user.UDynGroupMembership;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 
-public interface Group extends Relatable<Group, AnyObject, GRelationship> {
+public interface Group extends Relatable<Group, GRelationship> {
 
     String getName();
 
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/user/User.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/user/User.java
index ca74049a7a..4fe4171009 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/user/User.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/user/User.java
@@ -24,12 +24,11 @@ import java.util.Optional;
 import org.apache.syncope.core.persistence.api.entity.Groupable;
 import org.apache.syncope.core.persistence.api.entity.Relatable;
 import org.apache.syncope.core.persistence.api.entity.Role;
-import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 
 public interface User extends
         Account,
-        Groupable<User, UMembership, AnyObject, URelationship>,
-        Relatable<User, AnyObject, URelationship> {
+        Groupable<User, UMembership, URelationship>,
+        Relatable<User, URelationship> {
 
     String getToken();
 
diff --git 
a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractAnyMatchDAO.java
 
b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractAnyMatchDAO.java
index eadd640bc1..c0471cd262 100644
--- 
a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractAnyMatchDAO.java
+++ 
b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractAnyMatchDAO.java
@@ -213,7 +213,7 @@ public abstract class AbstractAnyMatchDAO implements 
AnyMatchDAO {
     }
 
     protected boolean matches(
-            final Relatable<?, ?, ?> any, final RelationshipTypeCond cond, 
final boolean not) {
+            final Relatable<?, ?> any, final RelationshipTypeCond cond, final 
boolean not) {
 
         boolean found = any.getRelationships().stream().
                 anyMatch(rel -> 
rel.getType().getKey().equals(cond.getRelationshipTypeKey()));
@@ -221,7 +221,7 @@ public abstract class AbstractAnyMatchDAO implements 
AnyMatchDAO {
     }
 
     protected boolean matches(
-            final Relatable<?, ?, ?> any, final RelationshipCond cond, final 
boolean not) {
+            final Relatable<?, ?> any, final RelationshipCond cond, final 
boolean not) {
 
         Set<String> candidates = 
SyncopeConstants.UUID_PATTERN.matcher(cond.getAnyObject()).matches()
                 ? Set.of(cond.getAnyObject())
@@ -237,7 +237,7 @@ public abstract class AbstractAnyMatchDAO implements 
AnyMatchDAO {
     }
 
     protected boolean matches(
-            final Groupable<?, ?, ?, ?> any, final MembershipCond cond, final 
boolean not) {
+            final Groupable<?, ?, ?> any, final MembershipCond cond, final 
boolean not) {
 
         final String group = 
SyncopeConstants.UUID_PATTERN.matcher(cond.getGroup()).matches()
                 ? cond.getGroup()
@@ -266,7 +266,7 @@ public abstract class AbstractAnyMatchDAO implements 
AnyMatchDAO {
     protected boolean matches(final Group group, final MemberCond cond, final 
boolean not) {
         boolean found = false;
 
-        Groupable<?, ?, ?, ?> any = 
userDAO.findById(cond.getMember()).orElse(null);
+        Groupable<?, ?, ?> any = 
userDAO.findById(cond.getMember()).orElse(null);
         if (any == null) {
             any = anyObjectDAO.findById(cond.getMember()).orElse(null);
             if (any != null) {
diff --git 
a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/DefaultAnyUtils.java
 
b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/DefaultAnyUtils.java
index 696980e5d3..f8f8fd53fa 100644
--- 
a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/DefaultAnyUtils.java
+++ 
b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/DefaultAnyUtils.java
@@ -18,6 +18,10 @@
  */
 package org.apache.syncope.core.persistence.common.entity;
 
+import static org.apache.syncope.common.lib.types.AnyTypeKind.ANY_OBJECT;
+import static org.apache.syncope.common.lib.types.AnyTypeKind.GROUP;
+import static org.apache.syncope.common.lib.types.AnyTypeKind.USER;
+
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.Collection;
@@ -56,8 +60,13 @@ import 
org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+import org.apache.syncope.core.persistence.api.entity.Relatable;
+import org.apache.syncope.core.persistence.api.entity.RelationshipType;
+import org.apache.syncope.core.persistence.api.entity.anyobject.ARelationship;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
+import org.apache.syncope.core.persistence.api.entity.group.GRelationship;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
+import org.apache.syncope.core.persistence.api.entity.user.URelationship;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -337,4 +346,57 @@ public class DefaultAnyUtils implements AnyUtils {
                 },
                 () -> LOG.warn("Any {} does not contain {} PLAIN attribute", 
key, schema.getKey()));
     }
+
+    @Transactional
+    @Override
+    public void addRelationship(
+            final Relatable<?, ?> relatable,
+            final RelationshipType relationshipType,
+            final AnyObject otherEnd) {
+
+        switch (anyTypeKind) {
+            case USER -> {
+                URelationship urelationship = 
entityFactory.newEntity(URelationship.class);
+                urelationship.setType(relationshipType);
+                urelationship.setRightEnd(otherEnd);
+                urelationship.setLeftEnd((User) relatable);
+
+                ((User) relatable).add(urelationship);
+            }
+
+            case GROUP -> {
+                GRelationship grelationship = 
entityFactory.newEntity(GRelationship.class);
+                grelationship.setType(relationshipType);
+                grelationship.setRightEnd(otherEnd);
+                grelationship.setLeftEnd((Group) relatable);
+
+                ((Group) relatable).add(grelationship);
+            }
+
+            case ANY_OBJECT -> {
+                ARelationship arelationship = 
entityFactory.newEntity(ARelationship.class);
+                arelationship.setType(relationshipType);
+                arelationship.setRightEnd(otherEnd);
+                arelationship.setLeftEnd((AnyObject) relatable);
+
+                ((AnyObject) relatable).add(arelationship);
+            }
+
+            default -> {
+            }
+        }
+    }
+
+    @Transactional
+    @Override
+    public void removeRelationship(
+            final Relatable<?, ?> relatable,
+            final RelationshipType relationshipType,
+            final String otherEndKey) {
+
+        relatable.getRelationship(relationshipType, 
otherEndKey).ifPresent(relationship -> {
+            relatable.getRelationships().remove(relationship);
+            relationship.setLeftEnd(null);
+        });
+    }
 }
diff --git 
a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/AnyValidator.java
 
b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/AnyValidator.java
index e6a841848f..63ab8b69e6 100644
--- 
a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/AnyValidator.java
+++ 
b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/AnyValidator.java
@@ -66,7 +66,7 @@ public class AnyValidator extends AbstractValidator<AnyCheck, 
Any> {
                 return raiseNotAllowedViolation(context, plainSchema, null);
             }
         }
-        if (any instanceof Groupable<?, ?, ?, ?> groupableRelatable) {
+        if (any instanceof Groupable<?, ?, ?> groupableRelatable) {
             for (Membership<?> membership : 
groupableRelatable.getMemberships()) {
                 for (PlainAttr attr : 
groupableRelatable.getPlainAttrs(membership)) {
                     String plainSchema = 
Optional.ofNullable(attr).map(PlainAttr::getSchema).orElse(null);
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractGroupableRelatable.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractGroupableRelatable.java
index 79008fa7eb..c6e3417a44 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractGroupableRelatable.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractGroupableRelatable.java
@@ -26,13 +26,13 @@ import 
org.apache.syncope.core.persistence.api.entity.Groupable;
 import org.apache.syncope.core.persistence.api.entity.Membership;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
 import org.apache.syncope.core.persistence.api.entity.Relationship;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 
 public abstract class AbstractGroupableRelatable<
         L extends Any, 
         M extends Membership<L>, 
-        R extends Any,
-        REL extends Relationship<L, R>>
-        extends AbstractRelatable<L, R, REL> implements Groupable<L, M, R, 
REL> {
+        REL extends Relationship<L, AnyObject>>
+        extends AbstractRelatable<L, REL> implements Groupable<L, M, REL> {
 
     private static final long serialVersionUID = -2269285197388729673L;
 
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractRelatable.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractRelatable.java
index 0f19b81a4c..ea66591fa7 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractRelatable.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/AbstractRelatable.java
@@ -24,12 +24,12 @@ import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.Relatable;
 import org.apache.syncope.core.persistence.api.entity.Relationship;
 import org.apache.syncope.core.persistence.api.entity.RelationshipType;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 
 public abstract class AbstractRelatable<
         L extends Any,
-        R extends Any,
-        REL extends Relationship<L, R>>
-        extends AbstractAny implements Relatable<L, R, REL> {
+        REL extends Relationship<L, AnyObject>>
+        extends AbstractAny implements Relatable<L, REL> {
 
     private static final long serialVersionUID = -2269285197388729673L;
 
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAAnyObject.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAAnyObject.java
index 0a5d0e6e24..623996dbd9 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAAnyObject.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/anyobject/JPAAnyObject.java
@@ -56,7 +56,7 @@ import 
org.apache.syncope.core.persistence.jpa.entity.JPAExternalResource;
 @Cacheable
 @AnyObjectCheck
 public class JPAAnyObject
-        extends AbstractGroupableRelatable<AnyObject, AMembership, AnyObject, 
ARelationship>
+        extends AbstractGroupableRelatable<AnyObject, AMembership, 
ARelationship>
         implements AnyObject {
 
     private static final long serialVersionUID = 9063766472970643492L;
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroup.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroup.java
index 274071e4a1..da5cee7af7 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroup.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/group/JPAGroup.java
@@ -45,7 +45,6 @@ import 
org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
 import 
org.apache.syncope.core.persistence.api.entity.anyobject.ADynGroupMembership;
-import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.group.GRelationship;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.group.TypeExtension;
@@ -65,7 +64,7 @@ import 
org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
 @Cacheable
 @GroupCheck
 public class JPAGroup
-        extends AbstractRelatable<Group, AnyObject, GRelationship>
+        extends AbstractRelatable<Group, GRelationship>
         implements Group {
 
     private static final long serialVersionUID = -5281258853142421875L;
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java
index 75c3f62868..319abbda38 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/user/JPAUser.java
@@ -53,7 +53,6 @@ import 
org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
 import org.apache.syncope.core.persistence.api.entity.Role;
-import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
 import org.apache.syncope.core.persistence.api.entity.user.SecurityQuestion;
 import org.apache.syncope.core.persistence.api.entity.user.UMembership;
@@ -72,7 +71,7 @@ import 
org.apache.syncope.core.spring.security.SecureRandomUtils;
 @EntityListeners({ JSONUserListener.class })
 @Cacheable
 public class JPAUser
-        extends AbstractGroupableRelatable<User, UMembership, AnyObject, 
URelationship>
+        extends AbstractGroupableRelatable<User, UMembership, URelationship>
         implements User {
 
     private static final long serialVersionUID = -3905046855521446823L;
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractGroupableRelatable.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractGroupableRelatable.java
index 8fbc5d9c03..7a1f7723c4 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractGroupableRelatable.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractGroupableRelatable.java
@@ -27,11 +27,14 @@ import 
org.apache.syncope.core.persistence.api.entity.Groupable;
 import org.apache.syncope.core.persistence.api.entity.Membership;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
 import org.apache.syncope.core.persistence.api.entity.Relationship;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.springframework.data.neo4j.core.schema.PostLoad;
 
 public abstract class AbstractGroupableRelatable<
-        L extends Any, M extends Membership<L>, R extends Any, REL extends 
Relationship<L, R>>
-        extends AbstractRelatable<L, R, REL> implements Groupable<L, M, R, 
REL> {
+        L extends Any, 
+        M extends Membership<L>, 
+        REL extends Relationship<L, AnyObject>>
+        extends AbstractRelatable<L, REL> implements Groupable<L, M, REL> {
 
     private static final long serialVersionUID = -2269285197388729673L;
 
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelatable.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelatable.java
index 3ed17ada0b..59d48a7d4c 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelatable.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractRelatable.java
@@ -24,12 +24,12 @@ import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.Relatable;
 import org.apache.syncope.core.persistence.api.entity.Relationship;
 import org.apache.syncope.core.persistence.api.entity.RelationshipType;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 
 public abstract class AbstractRelatable<
         L extends Any,
-        R extends Any,
-        REL extends Relationship<L, R>>
-        extends AbstractAny implements Relatable<L, R, REL> {
+        REL extends Relationship<L, AnyObject>>
+        extends AbstractAny implements Relatable<L, REL> {
 
     private static final long serialVersionUID = -2269285197388729673L;
 
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jAnyObject.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jAnyObject.java
index 6890b864e6..73890ab184 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jAnyObject.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/anyobject/Neo4jAnyObject.java
@@ -44,7 +44,7 @@ import 
org.springframework.data.neo4j.core.schema.Relationship;
 @AnyObjectCheck
 @AttributableCheck
 public class Neo4jAnyObject
-        extends AbstractGroupableRelatable<AnyObject, AMembership, AnyObject, 
ARelationship>
+        extends AbstractGroupableRelatable<AnyObject, AMembership, 
ARelationship>
         implements AnyObject {
 
     private static final long serialVersionUID = -3905046855521446823L;
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jGroup.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jGroup.java
index 836654b39a..ae89c503c0 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jGroup.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/group/Neo4jGroup.java
@@ -31,7 +31,6 @@ import 
org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
 import 
org.apache.syncope.core.persistence.api.entity.anyobject.ADynGroupMembership;
-import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.group.GRelationship;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.group.TypeExtension;
@@ -53,7 +52,7 @@ import 
org.springframework.data.neo4j.core.schema.Relationship;
 @GroupCheck
 @AttributableCheck
 public class Neo4jGroup
-        extends AbstractRelatable<Group, AnyObject, GRelationship>
+        extends AbstractRelatable<Group, GRelationship>
         implements Group {
 
     private static final long serialVersionUID = -5281258853142421875L;
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jUser.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jUser.java
index 390fdb3d62..d1cebb24e2 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jUser.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/user/Neo4jUser.java
@@ -37,7 +37,6 @@ import 
org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
 import org.apache.syncope.core.persistence.api.entity.Role;
-import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
 import org.apache.syncope.core.persistence.api.entity.user.SecurityQuestion;
 import org.apache.syncope.core.persistence.api.entity.user.UMembership;
@@ -58,7 +57,7 @@ import 
org.springframework.data.neo4j.core.schema.Relationship;
 @Node(Neo4jUser.NODE)
 @AttributableCheck
 public class Neo4jUser
-        extends AbstractGroupableRelatable<User, UMembership, AnyObject, 
URelationship>
+        extends AbstractGroupableRelatable<User, UMembership, URelationship>
         implements User {
 
     private static final long serialVersionUID = -3905046855521446823L;
diff --git 
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/DerAttrHandler.java
 
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/DerAttrHandler.java
index b4a6661993..f9863946a9 100644
--- 
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/DerAttrHandler.java
+++ 
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/DerAttrHandler.java
@@ -79,5 +79,5 @@ public interface DerAttrHandler {
      * @param membership membership
      * @return derived attribute values
      */
-    Map<DerSchema, String> getValues(Groupable<?, ?, ?, ?> any, Membership<?> 
membership);
+    Map<DerSchema, String> getValues(Groupable<?, ?, ?> any, Membership<?> 
membership);
 }
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultDerAttrHandler.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultDerAttrHandler.java
index 3233e6abe2..3741da47da 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultDerAttrHandler.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultDerAttrHandler.java
@@ -116,7 +116,7 @@ public class DefaultDerAttrHandler implements 
DerAttrHandler {
     }
 
     protected static Map<DerSchema, String> getValues(
-            final Groupable<?, ?, ?, ?> any, final Membership<?> membership, 
final Set<DerSchema> schemas) {
+            final Groupable<?, ?, ?> any, final Membership<?> membership, 
final Set<DerSchema> schemas) {
 
         Map<DerSchema, String> result = new HashMap<>(schemas.size());
 
@@ -132,7 +132,7 @@ public class DefaultDerAttrHandler implements 
DerAttrHandler {
     }
 
     @Override
-    public Map<DerSchema, String> getValues(final Groupable<?, ?, ?, ?> any, 
final Membership<?> membership) {
+    public Map<DerSchema, String> getValues(final Groupable<?, ?, ?> any, 
final Membership<?> membership) {
         return getValues(
                 any,
                 membership,
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java
index ef2649382b..9964916f28 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java
@@ -704,7 +704,7 @@ public class DefaultMappingManager implements 
MappingManager {
             } else {
                 references.add(user);
             }
-        } else if (intAttrName.getRelatedAnyObject() != null && any instanceof 
Relatable<?, ?, ?> relatable) {
+        } else if (intAttrName.getRelatedAnyObject() != null && any instanceof 
Relatable<?, ?> relatable) {
             AnyObject anyObject = 
anyObjectDAO.findById(intAttrName.getRelatedAnyObject()).orElse(null);
             if (anyObject == null || 
relatable.getRelationships(anyObject.getKey()).isEmpty()) {
                 LOG.warn("No relationship for {} in {}, ignoring", 
intAttrName.getRelatedAnyObject(), relatable);
@@ -712,7 +712,7 @@ public class DefaultMappingManager implements 
MappingManager {
                 references.add(anyObject);
             }
         } else if (intAttrName.getRelationshipAnyType() != null && 
intAttrName.getRelationshipType() != null
-                && any instanceof Relatable<?, ?, ?> relatable) {
+                && any instanceof Relatable<?, ?> relatable) {
 
             RelationshipType relationshipType = relationshipTypeDAO.findById(
                     intAttrName.getRelationshipType()).orElse(null);
@@ -727,7 +727,7 @@ public class DefaultMappingManager implements 
MappingManager {
                         map(Relationship::getRightEnd).
                         toList());
             }
-        } else if (intAttrName.getMembershipOfGroup() != null && any 
instanceof Groupable<?, ?, ?, ?> groupable) {
+        } else if (intAttrName.getMembershipOfGroup() != null && any 
instanceof Groupable<?, ?, ?> groupable) {
             membership = 
groupDAO.findByName(intAttrName.getMembershipOfGroup()).
                     flatMap(group -> groupable.getMembership(group.getKey())).
                     orElse(null);
@@ -829,7 +829,7 @@ public class DefaultMappingManager implements 
MappingManager {
                     case PLAIN -> {
                         PlainAttr attr = membership == null
                                 ? plainAttrGetter.apply(ref, 
intAttrName.getSchema().getKey())
-                                : ((Groupable<?, ?, ?, ?>) ref).getPlainAttr(
+                                : ((Groupable<?, ?, ?>) ref).getPlainAttr(
                                         intAttrName.getSchema().getKey(), 
membership).orElse(null);
                         if (attr != null) {
                             if (attr.getUniqueValue() != null) {
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyDataBinder.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyDataBinder.java
index 1ddf57dcef..cc3df75852 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyDataBinder.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyDataBinder.java
@@ -22,6 +22,7 @@ import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -35,12 +36,14 @@ import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.request.AnyCR;
 import org.apache.syncope.common.lib.request.AnyUR;
 import org.apache.syncope.common.lib.request.AttrPatch;
+import org.apache.syncope.common.lib.request.RelationshipUR;
 import org.apache.syncope.common.lib.request.StringPatchItem;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.ConnObject;
 import org.apache.syncope.common.lib.to.MembershipTO;
 import org.apache.syncope.common.lib.to.Provision;
 import org.apache.syncope.common.lib.to.RelationshipTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.PatchOperation;
@@ -57,6 +60,7 @@ import 
org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
 import org.apache.syncope.core.persistence.api.dao.RelationshipTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.Any;
+import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
@@ -68,6 +72,8 @@ import 
org.apache.syncope.core.persistence.api.entity.Membership;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+import org.apache.syncope.core.persistence.api.entity.Relatable;
+import org.apache.syncope.core.persistence.api.entity.RelationshipType;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.user.User;
@@ -305,7 +311,7 @@ abstract class AnyDataBinder extends AttributableDataBinder 
{
         AllowedSchemas<PlainSchema> allowedPlainSchemas = 
anyUtils.dao().findAllowedSchemas(any, PlainSchema.class);
         allowedPlainSchemas.getForSelf().forEach(schema -> checkMandatory(
                 schema, any.getPlainAttr(schema.getKey()).orElse(null), any, 
reqValMissing));
-        if (any instanceof Groupable<?, ?, ?, ?> groupable) {
+        if (any instanceof Groupable<?, ?, ?> groupable) {
             allowedPlainSchemas.getForMemberships().forEach((group, schemas) 
-> {
                 Membership<?> membership = 
groupable.getMembership(group.getKey()).orElse(null);
                 schemas.forEach(schema -> checkMandatory(
@@ -443,6 +449,61 @@ abstract class AnyDataBinder extends 
AttributableDataBinder {
         if (!reqValMissing.isEmpty()) {
             scce.addException(reqValMissing);
         }
+
+        // relationships
+        Set<Pair<String, String>> relationships = new HashSet<>();
+        for (RelationshipUR patch : anyUR.getRelationships().stream().
+                filter(patch -> patch.getRelationshipTO() != null).toList()) {
+
+            RelationshipType relationshipType = 
relationshipTypeDAO.findById(patch.getRelationshipTO().getType()).
+                    orElse(null);
+            if (relationshipType == null) {
+                LOG.debug("Ignoring invalid relationship type {}", 
patch.getRelationshipTO().getType());
+            } else {
+                anyUtils.removeRelationship(
+                        (Relatable<?, ?>) any,
+                        relationshipType,
+                        patch.getRelationshipTO().getOtherEndKey());
+
+                if (patch.getOperation() == PatchOperation.ADD_REPLACE) {
+                    if 
(StringUtils.isBlank(patch.getRelationshipTO().getOtherEndType())
+                            || 
AnyTypeKind.USER.name().equals(patch.getRelationshipTO().getOtherEndType())
+                            || 
AnyTypeKind.GROUP.name().equals(patch.getRelationshipTO().getOtherEndType())) {
+
+                        SyncopeClientException invalidAnyType =
+                                
SyncopeClientException.build(ClientExceptionType.InvalidAnyType);
+                        
invalidAnyType.getElements().add(AnyType.class.getSimpleName()
+                                + " not allowed for relationship: " + 
patch.getRelationshipTO().getOtherEndType());
+                        scce.addException(invalidAnyType);
+                    } else {
+                        AnyObject otherEnd = 
anyObjectDAO.findById(patch.getRelationshipTO().getOtherEndKey()).
+                                orElse(null);
+                        if (otherEnd == null) {
+                            LOG.debug("Ignoring invalid any object {}", 
patch.getRelationshipTO().getOtherEndKey());
+                        } else if (relationships.contains(
+                                Pair.of(otherEnd.getKey(), 
patch.getRelationshipTO().getType()))) {
+
+                            SyncopeClientException assigned =
+                                    
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
+                            assigned.getElements().add("Group was already in 
relationship "
+                                    + patch.getRelationshipTO().getType() + " 
with "
+                                    + otherEnd.getType().getKey() + " " + 
otherEnd.getName());
+                            scce.addException(assigned);
+                        } else if (patch.getRelationshipTO().getEnd() == 
RelationshipTO.End.RIGHT) {
+                            SyncopeClientException noRight =
+                                    
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
+                            noRight.getElements().add(
+                                    "Relationships shall be created or updated 
only from their left end");
+                            scce.addException(noRight);
+                        } else {
+                            relationships.add(Pair.of(otherEnd.getKey(), 
patch.getRelationshipTO().getType()));
+
+                            anyUtils.addRelationship((Relatable<?, ?>) any, 
relationshipType, otherEnd);
+                        }
+                    }
+                }
+            }
+        }
     }
 
     protected PropagationByResource<String> propByRes(
@@ -528,6 +589,44 @@ abstract class AnyDataBinder extends 
AttributableDataBinder {
         if (!requiredValuesMissing.isEmpty()) {
             scce.addException(requiredValuesMissing);
         }
+
+        // 3. relationships
+        Set<Pair<String, String>> relationships = new HashSet<>();
+        anyCR.getRelationships().forEach(relationshipTO -> {
+            if (StringUtils.isBlank(relationshipTO.getOtherEndType())
+                    || 
AnyTypeKind.USER.name().equals(relationshipTO.getOtherEndType())
+                    || 
AnyTypeKind.GROUP.name().equals(relationshipTO.getOtherEndType())) {
+
+                SyncopeClientException invalidAnyType =
+                        
SyncopeClientException.build(ClientExceptionType.InvalidAnyType);
+                invalidAnyType.getElements().add(AnyType.class.getSimpleName()
+                        + " not allowed for relationship: " + 
relationshipTO.getOtherEndType());
+                scce.addException(invalidAnyType);
+            } else {
+                AnyObject otherEnd = 
anyObjectDAO.findById(relationshipTO.getOtherEndKey()).orElse(null);
+                if (otherEnd == null) {
+                    LOG.debug("Ignoring invalid anyObject {}", 
relationshipTO.getOtherEndKey());
+                } else if (relationshipTO.getEnd() == 
RelationshipTO.End.RIGHT) {
+                    SyncopeClientException noRight =
+                            
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
+                    noRight.getElements().add(
+                            "Relationships shall be created or updated only 
from their left end");
+                    scce.addException(noRight);
+                } else if (relationships.contains(Pair.of(otherEnd.getKey(), 
relationshipTO.getType()))) {
+                    SyncopeClientException assigned =
+                            
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
+                    assigned.getElements().add(otherEnd.getType().getKey() + " 
" + otherEnd.getName()
+                            + " in relationship " + relationshipTO.getType());
+                    scce.addException(assigned);
+                } else {
+                    relationships.add(Pair.of(otherEnd.getKey(), 
relationshipTO.getType()));
+
+                    
relationshipTypeDAO.findById(relationshipTO.getType()).ifPresentOrElse(
+                            rt -> anyUtils.addRelationship((Relatable<?, ?>) 
any, rt, otherEnd),
+                            () -> LOG.debug("Ignoring invalid relationship 
type {}", relationshipTO.getType()));
+                }
+            }
+        });
     }
 
     protected void fill(
@@ -543,7 +642,7 @@ abstract class AnyDataBinder extends AttributableDataBinder 
{
                 filter(attrTO -> !attrTO.getValues().isEmpty()).
                 forEach(attrTO -> 
getPlainSchema(attrTO.getSchema()).ifPresent(schema -> {
 
-            PlainAttr attr = ((Groupable<?, ?, ?, ?>) 
any).getPlainAttr(schema.getKey(), membership).orElseGet(() -> {
+            PlainAttr attr = ((Groupable<?, ?, ?>) 
any).getPlainAttr(schema.getKey(), membership).orElseGet(() -> {
                 PlainAttr gpa = new PlainAttr();
                 gpa.setMembership(membership.getKey());
                 gpa.setPlainSchema(schema);
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
index d9e0056209..bec1013f37 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
@@ -22,7 +22,6 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.AnyOperations;
 import org.apache.syncope.common.lib.EntityTOUtils;
 import org.apache.syncope.common.lib.SyncopeClientCompositeException;
@@ -55,9 +54,7 @@ import 
org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.Realm;
-import org.apache.syncope.core.persistence.api.entity.RelationshipType;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership;
-import org.apache.syncope.core.persistence.api.entity.anyobject.ARelationship;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.provisioning.api.DerAttrHandler;
@@ -205,51 +202,8 @@ public class AnyObjectDataBinderImpl extends AnyDataBinder 
implements AnyObjectD
         }
         anyObject.setRealm(realm);
 
-        // relationships
-        Set<Pair<String, String>> relationships = new HashSet<>();
-        anyObjectCR.getRelationships().forEach(relationshipTO -> {
-            if (StringUtils.isBlank(relationshipTO.getOtherEndType())
-                    || 
AnyTypeKind.USER.name().equals(relationshipTO.getOtherEndType())
-                    || 
AnyTypeKind.GROUP.name().equals(relationshipTO.getOtherEndType())) {
-
-                SyncopeClientException invalidAnyType =
-                        
SyncopeClientException.build(ClientExceptionType.InvalidAnyType);
-                invalidAnyType.getElements().add(AnyType.class.getSimpleName()
-                        + " not allowed for relationship: " + 
relationshipTO.getOtherEndType());
-                scce.addException(invalidAnyType);
-            } else {
-                AnyObject otherEnd = 
anyObjectDAO.findById(relationshipTO.getOtherEndKey()).orElse(null);
-                if (otherEnd == null) {
-                    LOG.debug("Ignoring invalid anyObject {}", 
relationshipTO.getOtherEndKey());
-                } else if (relationshipTO.getEnd() == 
RelationshipTO.End.RIGHT) {
-                    SyncopeClientException noRight =
-                            
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
-                    noRight.getElements().add(
-                            "Relationships shall be created or updated only 
from their left end");
-                    scce.addException(noRight);
-                } else if (relationships.contains(Pair.of(otherEnd.getKey(), 
relationshipTO.getType()))) {
-                    SyncopeClientException assigned =
-                            
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
-                    assigned.getElements().add("AnyObject was already in 
relationship "
-                            + relationshipTO.getType() + " with "
-                            + otherEnd.getType().getKey() + " " + 
otherEnd.getName());
-                    scce.addException(assigned);
-                } else {
-                    relationships.add(Pair.of(otherEnd.getKey(), 
relationshipTO.getType()));
-
-                    
relationshipTypeDAO.findById(relationshipTO.getType()).ifPresentOrElse(
-                            relationshipType -> {
-                                ARelationship relationship = 
entityFactory.newEntity(ARelationship.class);
-                                relationship.setType(relationshipType);
-                                relationship.setRightEnd(otherEnd);
-                                relationship.setLeftEnd(anyObject);
-
-                                anyObject.add(relationship);
-                            },
-                            () -> LOG.debug("Ignoring invalid relationship 
type {}", relationshipTO.getType()));
-                }
-            }
-        });
+        // attributes, resources and relationships
+        fill(anyTO, anyObject, anyObjectCR, 
anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT), scce);
 
         // memberships
         Set<String> groups = new HashSet<>();
@@ -280,9 +234,6 @@ public class AnyObjectDataBinderImpl extends AnyDataBinder 
implements AnyObjectD
             }
         });
 
-        // attributes and resources
-        fill(anyTO, anyObject, anyObjectCR, 
anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT), scce);
-
         // Throw composite exception if there is at least one element set in 
the composing exceptions
         if (scce.hasExceptions()) {
             throw scce;
@@ -314,70 +265,9 @@ public class AnyObjectDataBinderImpl extends AnyDataBinder 
implements AnyObjectD
             anyObject.setName(anyObjectUR.getName().getValue());
         }
 
-        // attributes and resources
+        // attributes, resources and relationships
         fill(anyTO, anyObject, anyObjectUR, anyUtils, scce);
 
-        // relationships
-        Set<Pair<String, String>> relationships = new HashSet<>();
-        anyObjectUR.getRelationships().stream().
-                filter(patch -> patch.getRelationshipTO() != 
null).forEach(patch -> {
-
-            RelationshipType relationshipType = relationshipTypeDAO.findById(
-                    patch.getRelationshipTO().getType()).orElse(null);
-            if (relationshipType == null) {
-                LOG.debug("Ignoring invalid relationship type {}", 
patch.getRelationshipTO().getType());
-            } else {
-                anyObject.getRelationship(relationshipType, 
patch.getRelationshipTO().getOtherEndKey()).
-                        ifPresent(relationship -> {
-                            anyObject.getRelationships().remove(relationship);
-                            relationship.setLeftEnd(null);
-                        });
-
-                if (patch.getOperation() == PatchOperation.ADD_REPLACE) {
-                    if 
(StringUtils.isBlank(patch.getRelationshipTO().getOtherEndType())
-                            || 
AnyTypeKind.USER.name().equals(patch.getRelationshipTO().getOtherEndType())
-                            || 
AnyTypeKind.GROUP.name().equals(patch.getRelationshipTO().getOtherEndType())) {
-
-                        SyncopeClientException invalidAnyType =
-                                
SyncopeClientException.build(ClientExceptionType.InvalidAnyType);
-                        
invalidAnyType.getElements().add(AnyType.class.getSimpleName()
-                                + " not allowed for relationship: " + 
patch.getRelationshipTO().getOtherEndType());
-                        scce.addException(invalidAnyType);
-                    } else {
-                        AnyObject otherEnd = 
anyObjectDAO.findById(patch.getRelationshipTO().getOtherEndKey()).
-                                orElse(null);
-                        if (otherEnd == null) {
-                            LOG.debug("Ignoring invalid any object {}", 
patch.getRelationshipTO().getOtherEndKey());
-                        } else if (patch.getRelationshipTO().getEnd() == 
RelationshipTO.End.RIGHT) {
-                            SyncopeClientException noRight =
-                                    
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
-                            noRight.getElements().add(
-                                    "Relationships shall be created or updated 
only from their left end");
-                            scce.addException(noRight);
-                        } else if (relationships.contains(
-                                Pair.of(otherEnd.getKey(), 
patch.getRelationshipTO().getType()))) {
-
-                            SyncopeClientException assigned =
-                                    
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
-                            assigned.getElements().add("AnyObject was already 
in relationship "
-                                    + patch.getRelationshipTO().getType() + " 
with "
-                                    + otherEnd.getType().getKey() + " " + 
otherEnd.getName());
-                            scce.addException(assigned);
-                        } else {
-                            relationships.add(Pair.of(otherEnd.getKey(), 
patch.getRelationshipTO().getType()));
-
-                            ARelationship newRelationship = 
entityFactory.newEntity(ARelationship.class);
-                            newRelationship.setType(relationshipType);
-                            newRelationship.setRightEnd(otherEnd);
-                            newRelationship.setLeftEnd(anyObject);
-
-                            anyObject.add(newRelationship);
-                        }
-                    }
-                }
-            }
-        });
-
         SyncopeClientException invalidValues = 
SyncopeClientException.build(ClientExceptionType.InvalidValues);
 
         // memberships
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
index 9ede39b550..e8508c4e6e 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
@@ -19,27 +19,23 @@
 package org.apache.syncope.core.provisioning.java.data;
 
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.AnyOperations;
 import org.apache.syncope.common.lib.EntityTOUtils;
 import org.apache.syncope.common.lib.SyncopeClientCompositeException;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.request.GroupCR;
 import org.apache.syncope.common.lib.request.GroupUR;
-import org.apache.syncope.common.lib.request.RelationshipUR;
 import org.apache.syncope.common.lib.to.ConnObject;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.RelationshipTO;
 import org.apache.syncope.common.lib.to.TypeExtensionTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
-import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import 
org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
@@ -59,10 +55,7 @@ import 
org.apache.syncope.core.persistence.api.entity.DynGroupMembership;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.Groupable;
 import org.apache.syncope.core.persistence.api.entity.Realm;
-import org.apache.syncope.core.persistence.api.entity.RelationshipType;
 import 
org.apache.syncope.core.persistence.api.entity.anyobject.ADynGroupMembership;
-import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
-import org.apache.syncope.core.persistence.api.entity.group.GRelationship;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.group.TypeExtension;
 import org.apache.syncope.core.persistence.api.entity.user.UDynGroupMembership;
@@ -179,7 +172,7 @@ public class GroupDataBinderImpl extends AnyDataBinder 
implements GroupDataBinde
         }
         group.setRealm(realm);
 
-        // attributes and resources
+        // attributes, resources and relationships
         fill(anyTO, group, groupCR, 
anyUtilsFactory.getInstance(AnyTypeKind.GROUP), scce);
 
         // owner
@@ -221,46 +214,6 @@ public class GroupDataBinderImpl extends AnyDataBinder 
implements GroupDataBinde
                 },
                 () -> LOG.warn("Ignoring invalid {}: {}", 
AnyType.class.getSimpleName(), typeExtTO.getAnyType())));
 
-        // relationships
-        Set<Pair<String, String>> relationships = new HashSet<>();
-        groupCR.getRelationships().forEach(relationshipTO -> {
-            AnyObject otherEnd = 
anyObjectDAO.findById(relationshipTO.getOtherEndKey()).orElse(null);
-            if (otherEnd == null) {
-                LOG.debug("Ignoring invalid anyObject {}", 
relationshipTO.getOtherEndKey());
-            } else if (relationshipTO.getEnd() == RelationshipTO.End.RIGHT) {
-                SyncopeClientException noRight =
-                        
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
-                noRight.getElements().add(
-                        "Relationships shall be created or updated only from 
their left end");
-                scce.addException(noRight);
-            } else if (relationships.contains(Pair.of(otherEnd.getKey(), 
relationshipTO.getType()))) {
-                SyncopeClientException assigned =
-                        
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
-                assigned.getElements().add(otherEnd.getType().getKey() + " " + 
otherEnd.getName()
-                        + " in relationship " + relationshipTO.getType());
-                scce.addException(assigned);
-            } else if 
(group.getRealm().getFullPath().startsWith(otherEnd.getRealm().getFullPath())) {
-                relationships.add(Pair.of(otherEnd.getKey(), 
relationshipTO.getType()));
-
-                
relationshipTypeDAO.findById(relationshipTO.getType()).ifPresentOrElse(
-                        relationshipType -> {
-                            GRelationship relationship = 
entityFactory.newEntity(GRelationship.class);
-                            relationship.setType(relationshipType);
-                            relationship.setRightEnd(otherEnd);
-                            relationship.setLeftEnd(group);
-
-                            group.add(relationship);
-                        },
-                        () -> LOG.debug("Ignoring invalid relationship type 
{}", relationshipTO.getType()));
-            } else {
-                SyncopeClientException unrelatable =
-                        
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
-                unrelatable.getElements().add(otherEnd.getType().getKey() + " 
" + otherEnd.getName()
-                        + " cannot be related");
-                scce.addException(unrelatable);
-            }
-        });
-
         // Throw composite exception if there is at least one element set in 
the composing exceptions
         if (scce.hasExceptions()) {
             throw scce;
@@ -327,7 +280,7 @@ public class GroupDataBinderImpl extends AnyDataBinder 
implements GroupDataBinde
             }
         }
 
-        // attributes and resources
+        // attributes, resources and relationships
         fill(anyTO, group, groupUR, 
anyUtilsFactory.getInstance(AnyTypeKind.GROUP), scce);
 
         group = groupDAO.save(group);
@@ -397,64 +350,6 @@ public class GroupDataBinderImpl extends AnyDataBinder 
implements GroupDataBinde
         group.getTypeExtensions().
                 removeIf(typeExt -> 
groupUR.getTypeExtension(typeExt.getAnyType().getKey()).isEmpty());
 
-        // relationships
-        Set<Pair<String, String>> relationships = new HashSet<>();
-        for (RelationshipUR patch : groupUR.getRelationships().stream().
-                filter(patch -> patch.getRelationshipTO() != null).toList()) {
-
-            RelationshipType relationshipType = 
relationshipTypeDAO.findById(patch.getRelationshipTO().getType()).
-                    orElse(null);
-            if (relationshipType == null) {
-                LOG.debug("Ignoring invalid relationship type {}", 
patch.getRelationshipTO().getType());
-            } else {
-                GRelationship relationship = group.getRelationship(
-                        relationshipType, 
patch.getRelationshipTO().getOtherEndKey()).orElse(null);
-                if (relationship != null) {
-                    group.getRelationships().remove(relationship);
-                    relationship.setLeftEnd(null);
-                }
-
-                if (patch.getOperation() == PatchOperation.ADD_REPLACE) {
-                    AnyObject otherEnd = 
anyObjectDAO.findById(patch.getRelationshipTO().getOtherEndKey()).orElse(null);
-                    if (otherEnd == null) {
-                        LOG.debug("Ignoring invalid any object {}", 
patch.getRelationshipTO().getOtherEndKey());
-                    } else if (relationships.contains(
-                            Pair.of(otherEnd.getKey(), 
patch.getRelationshipTO().getType()))) {
-
-                        SyncopeClientException assigned =
-                                
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
-                        assigned.getElements().add("Group was already in 
relationship "
-                                + patch.getRelationshipTO().getType() + " with 
"
-                                + otherEnd.getType().getKey() + " " + 
otherEnd.getName());
-                        scce.addException(assigned);
-                    } else if (patch.getRelationshipTO().getEnd() == 
RelationshipTO.End.RIGHT) {
-                        SyncopeClientException noRight =
-                                
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
-                        noRight.getElements().add(
-                                "Relationships shall be created or updated 
only from their left end");
-                        scce.addException(noRight);
-                    } else if 
(group.getRealm().getFullPath().startsWith(otherEnd.getRealm().getFullPath())) {
-                        relationships.add(Pair.of(otherEnd.getKey(), 
patch.getRelationshipTO().getType()));
-
-                        GRelationship newRelationship = 
entityFactory.newEntity(GRelationship.class);
-                        newRelationship.setType(relationshipType);
-                        newRelationship.setRightEnd(otherEnd);
-                        newRelationship.setLeftEnd(group);
-
-                        group.add(newRelationship);
-                    } else {
-                        LOG.error("{} cannot be related to {}", otherEnd, 
group);
-
-                        SyncopeClientException unrelatable =
-                                
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
-                        
unrelatable.getElements().add(otherEnd.getType().getKey() + " " + 
otherEnd.getName()
-                                + " cannot be related");
-                        scce.addException(unrelatable);
-                    }
-                }
-            }
-        }
-
         // Throw composite exception if there is at least one element set in 
the composing exceptions
         if (scce.hasExceptions()) {
             throw scce;
@@ -541,7 +436,7 @@ public class GroupDataBinderImpl extends AnyDataBinder 
implements GroupDataBinde
 
     protected static void populateTransitiveResources(
             final Group group,
-            final Groupable<?, ?, ?, ?> any,
+            final Groupable<?, ?, ?> any,
             final Map<String, PropagationByResource<String>> result) {
 
         PropagationByResource<String> propByRes = new 
PropagationByResource<>();
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
index f182ab4c39..c44800dd06 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
@@ -72,14 +72,11 @@ import 
org.apache.syncope.core.persistence.api.entity.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.Realm;
-import org.apache.syncope.core.persistence.api.entity.RelationshipType;
 import org.apache.syncope.core.persistence.api.entity.Role;
-import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
 import org.apache.syncope.core.persistence.api.entity.user.SecurityQuestion;
 import org.apache.syncope.core.persistence.api.entity.user.UMembership;
-import org.apache.syncope.core.persistence.api.entity.user.URelationship;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 import org.apache.syncope.core.provisioning.api.DerAttrHandler;
 import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
@@ -313,45 +310,8 @@ public class UserDataBinderImpl extends AnyDataBinder 
implements UserDataBinder
         }
         user.setRealm(realm);
 
-        // relationships
-        Set<Pair<String, String>> relationships = new HashSet<>();
-        userCR.getRelationships().forEach(relationshipTO -> {
-            AnyObject otherEnd = 
anyObjectDAO.findById(relationshipTO.getOtherEndKey()).orElse(null);
-            if (otherEnd == null) {
-                LOG.debug("Ignoring invalid anyObject {}", 
relationshipTO.getOtherEndKey());
-            } else if (relationshipTO.getEnd() == RelationshipTO.End.RIGHT) {
-                SyncopeClientException noRight =
-                        
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
-                noRight.getElements().add(
-                        "Relationships shall be created or updated only from 
their left end");
-                scce.addException(noRight);
-            } else if (relationships.contains(Pair.of(otherEnd.getKey(), 
relationshipTO.getType()))) {
-                SyncopeClientException assigned =
-                        
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
-                assigned.getElements().add(otherEnd.getType().getKey() + " " + 
otherEnd.getName()
-                        + " in relationship " + relationshipTO.getType());
-                scce.addException(assigned);
-            } else if 
(user.getRealm().getFullPath().startsWith(otherEnd.getRealm().getFullPath())) {
-                relationships.add(Pair.of(otherEnd.getKey(), 
relationshipTO.getType()));
-
-                
relationshipTypeDAO.findById(relationshipTO.getType()).ifPresentOrElse(
-                        relationshipType -> {
-                            URelationship relationship = 
entityFactory.newEntity(URelationship.class);
-                            relationship.setType(relationshipType);
-                            relationship.setRightEnd(otherEnd);
-                            relationship.setLeftEnd(user);
-
-                            user.add(relationship);
-                        },
-                        () -> LOG.debug("Ignoring invalid relationship type 
{}", relationshipTO.getType()));
-            } else {
-                SyncopeClientException unrelatable =
-                        
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
-                unrelatable.getElements().add(otherEnd.getType().getKey() + " 
" + otherEnd.getName()
-                        + " cannot be related");
-                scce.addException(unrelatable);
-            }
-        });
+        // attributes, resources and relationships
+        fill(anyTO, user, userCR, 
anyUtilsFactory.getInstance(AnyTypeKind.USER), scce);
 
         // memberships
         Set<String> groups = new HashSet<>();
@@ -390,9 +350,6 @@ public class UserDataBinderImpl extends AnyDataBinder 
implements UserDataBinder
             scce.addException(invalidValues);
         }
 
-        // attributes and resources
-        fill(anyTO, user, userCR, 
anyUtilsFactory.getInstance(AnyTypeKind.USER), scce);
-
         // Throw composite exception if there is at least one element set in 
the composing exceptions
         if (scce.hasExceptions()) {
             throw scce;
@@ -498,64 +455,9 @@ public class UserDataBinderImpl extends AnyDataBinder 
implements UserDataBinder
                     () -> LOG.warn("Ignoring unknown role with key {}", 
patch.getValue()));
         }
 
-        // attributes and resources
+        // attributes, resources and relationships
         fill(anyTO, user, userUR, anyUtils, scce);
 
-        // relationships
-        Set<Pair<String, String>> relationships = new HashSet<>();
-        userUR.getRelationships().stream().filter(patch -> 
patch.getRelationshipTO() != null).forEach(patch -> {
-            RelationshipType relationshipType = 
relationshipTypeDAO.findById(patch.getRelationshipTO().getType()).
-                    orElse(null);
-            if (relationshipType == null) {
-                LOG.debug("Ignoring invalid relationship type {}", 
patch.getRelationshipTO().getType());
-            } else {
-                user.getRelationship(relationshipType, 
patch.getRelationshipTO().getOtherEndKey()).
-                        ifPresent(relationship -> {
-                            user.getRelationships().remove(relationship);
-                            relationship.setLeftEnd(null);
-                        });
-
-                if (patch.getOperation() == PatchOperation.ADD_REPLACE) {
-                    AnyObject otherEnd = 
anyObjectDAO.findById(patch.getRelationshipTO().getOtherEndKey()).orElse(null);
-                    if (otherEnd == null) {
-                        LOG.debug("Ignoring invalid any object {}", 
patch.getRelationshipTO().getOtherEndKey());
-                    } else if (relationships.contains(
-                            Pair.of(otherEnd.getKey(), 
patch.getRelationshipTO().getType()))) {
-
-                        SyncopeClientException assigned =
-                                
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
-                        assigned.getElements().add("User was already in 
relationship "
-                                + patch.getRelationshipTO().getType() + " with 
"
-                                + otherEnd.getType().getKey() + " " + 
otherEnd.getName());
-                        scce.addException(assigned);
-                    } else if (patch.getRelationshipTO().getEnd() == 
RelationshipTO.End.RIGHT) {
-                        SyncopeClientException noRight =
-                                
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
-                        noRight.getElements().add(
-                                "Relationships shall be created or updated 
only from their left end");
-                        scce.addException(noRight);
-                    } else if 
(user.getRealm().getFullPath().startsWith(otherEnd.getRealm().getFullPath())) {
-                        relationships.add(Pair.of(otherEnd.getKey(), 
patch.getRelationshipTO().getType()));
-
-                        URelationship newRelationship = 
entityFactory.newEntity(URelationship.class);
-                        newRelationship.setType(relationshipType);
-                        newRelationship.setRightEnd(otherEnd);
-                        newRelationship.setLeftEnd(user);
-
-                        user.add(newRelationship);
-                    } else {
-                        LOG.error("{} cannot be related to {}", otherEnd, 
user);
-
-                        SyncopeClientException unrelatable =
-                                
SyncopeClientException.build(ClientExceptionType.InvalidRelationship);
-                        
unrelatable.getElements().add(otherEnd.getType().getKey() + " " + 
otherEnd.getName()
-                                + " cannot be related");
-                        scce.addException(unrelatable);
-                    }
-                }
-            }
-        });
-
         SyncopeClientException invalidValues = 
SyncopeClientException.build(ClientExceptionType.InvalidValues);
 
         // memberships
diff --git 
a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
 
b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
index 68ad03f96e..ac667c3875 100644
--- 
a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
+++ 
b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
@@ -196,7 +196,7 @@ public class ElasticsearchUtils {
         }
 
         // add also flattened membership attributes
-        if (any instanceof Groupable<?, ?, ?, ?> groupable) {
+        if (any instanceof Groupable<?, ?, ?> groupable) {
             groupable.getMemberships().forEach(m -> 
groupable.getPlainAttrs(m).forEach(mAttr -> {
                 List<Object> values = mAttr.getValues().stream().
                         
map(PlainAttrValue::getValue).collect(Collectors.toList());
diff --git 
a/ext/openfga/client-openfga/src/main/java/org/apache/syncope/ext/openfga/client/OpenFGAStoreManager.java
 
b/ext/openfga/client-openfga/src/main/java/org/apache/syncope/ext/openfga/client/OpenFGAStoreManager.java
index c856624fd9..7c3319ad0b 100644
--- 
a/ext/openfga/client-openfga/src/main/java/org/apache/syncope/ext/openfga/client/OpenFGAStoreManager.java
+++ 
b/ext/openfga/client-openfga/src/main/java/org/apache/syncope/ext/openfga/client/OpenFGAStoreManager.java
@@ -240,14 +240,14 @@ public class OpenFGAStoreManager {
             next = List.of();
         } else {
             next = new ArrayList<>();
-            if (any instanceof Groupable<?, ?, ?, ?> groupable) {
+            if (any instanceof Groupable<?, ?, ?> groupable) {
                 next.addAll(groupable.getMemberships().stream().map(m -> new 
TupleKey().
                         user(id(groupable)).
                         relation(OpenFGAClientFactory.MEMBERSHIP_RELATION).
                         _object(id(m.getRightEnd()))).
                         toList());
             }
-            if (any instanceof Relatable<?, ?, ?> relatable) {
+            if (any instanceof Relatable<?, ?> relatable) {
                 next.addAll(relatable.getRelationships().stream().
                         filter(r -> 
!r.getType().getRightEndAnyType().equals(any.getType())).
                         map(r -> new TupleKey().
diff --git 
a/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java
 
b/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java
index 28f2f65eb8..10650a7ca3 100644
--- 
a/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java
+++ 
b/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java
@@ -196,7 +196,7 @@ public class OpenSearchUtils {
         }
 
         // add also flattened membership attributes
-        if (any instanceof Groupable<?, ?, ?, ?> groupable) {
+        if (any instanceof Groupable<?, ?, ?> groupable) {
             groupable.getMemberships().forEach(m -> 
groupable.getPlainAttrs(m).forEach(mAttr -> {
                 List<Object> values = mAttr.getValues().stream().
                         
map(PlainAttrValue::getValue).collect(Collectors.toList());

Reply via email to