diff --git a/src/main/java/sh/libre/scim/core/ScimDispatcher.java b/src/main/java/sh/libre/scim/core/ScimDispatcher.java
index b0142ee4ae3a868f532f0f5dbb4ce3d29fa91e43..4146c3011963389d45dddecd3cd3bc65bb78f1fb 100644
--- a/src/main/java/sh/libre/scim/core/ScimDispatcher.java
+++ b/src/main/java/sh/libre/scim/core/ScimDispatcher.java
@@ -7,7 +7,9 @@ import sh.libre.scim.core.exceptions.ScimExceptionHandler;
 import sh.libre.scim.core.exceptions.ScimPropagationException;
 import sh.libre.scim.core.exceptions.SkipOrStopApproach;
 import sh.libre.scim.core.exceptions.SkipOrStopStrategy;
-import sh.libre.scim.storage.ScimEndpointConfigurationStorageProviderFactory;
+import sh.libre.scim.core.service.AbstractScimService;
+import sh.libre.scim.core.service.GroupScimService;
+import sh.libre.scim.core.service.UserScimService;
 
 import java.util.ArrayList;
 import java.util.LinkedHashSet;
@@ -85,7 +87,7 @@ public class ScimDispatcher {
                 exceptionHandler.handleException(userScimService.getConfiguration(), e);
             }
         });
-        // TODO we could iterate on servicesCorrectlyPropagated to undo modification
+        // TODO we could iterate on servicesCorrectlyPropagated to undo modification on already handled SCIM endpoints
         LOGGER.infof("[SCIM] User operation dispatched to %d SCIM server", servicesCorrectlyPropagated.size());
     }
 
@@ -100,7 +102,7 @@ public class ScimDispatcher {
                 exceptionHandler.handleException(groupScimService.getConfiguration(), e);
             }
         });
-        // TODO we could iterate on servicesCorrectlyPropagated to undo modification
+        // TODO we could iterate on servicesCorrectlyPropagated to undo modification on already handled SCIM endpoints
         LOGGER.infof("[SCIM] Group operation dispatched to %d SCIM server", servicesCorrectlyPropagated.size());
     }
 
diff --git a/src/main/java/sh/libre/scim/storage/ScimEndpointConfigurationStorageProviderFactory.java b/src/main/java/sh/libre/scim/core/ScimEndpointConfigurationStorageProviderFactory.java
similarity index 80%
rename from src/main/java/sh/libre/scim/storage/ScimEndpointConfigurationStorageProviderFactory.java
rename to src/main/java/sh/libre/scim/core/ScimEndpointConfigurationStorageProviderFactory.java
index f825ab6651ab392f32d6f5cdab800192fe79d197..60f61b8c6f9b9075170c6c8af02fff5282825804 100644
--- a/src/main/java/sh/libre/scim/storage/ScimEndpointConfigurationStorageProviderFactory.java
+++ b/src/main/java/sh/libre/scim/core/ScimEndpointConfigurationStorageProviderFactory.java
@@ -1,11 +1,10 @@
-package sh.libre.scim.storage;
+package sh.libre.scim.core;
 
 import de.captaingoldfish.scim.sdk.common.constants.HttpHeader;
 import jakarta.ws.rs.core.MediaType;
 import org.apache.commons.lang3.BooleanUtils;
 import org.jboss.logging.Logger;
 import org.keycloak.component.ComponentModel;
-import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.RealmModel;
@@ -17,11 +16,8 @@ import org.keycloak.storage.UserStorageProviderFactory;
 import org.keycloak.storage.UserStorageProviderModel;
 import org.keycloak.storage.user.ImportSynchronization;
 import org.keycloak.storage.user.SynchronizationResult;
-import org.keycloak.timer.TimerProvider;
-import sh.libre.scim.core.ScimDispatcher;
-import sh.libre.scim.core.ScrimEndPointConfiguration;
+import sh.libre.scim.event.ScimBackgroundGroupMembershipUpdater;
 
-import java.time.Duration;
 import java.util.Date;
 import java.util.List;
 
@@ -31,7 +27,7 @@ import java.util.List;
 public class ScimEndpointConfigurationStorageProviderFactory
         implements UserStorageProviderFactory<ScimEndpointConfigurationStorageProviderFactory.ScimEndpointConfigurationStorageProvider>, ImportSynchronization {
     public static final String ID = "scim";
-    private final Logger LOGGER = Logger.getLogger(ScimEndpointConfigurationStorageProviderFactory.class);
+    private static final Logger LOGGER = Logger.getLogger(ScimEndpointConfigurationStorageProviderFactory.class);
 
     @Override
     public String getId() {
@@ -42,7 +38,7 @@ public class ScimEndpointConfigurationStorageProviderFactory
     @Override
     public SynchronizationResult sync(KeycloakSessionFactory sessionFactory, String realmId,
                                       UserStorageProviderModel model) {
-        // TODO if this should be kept here, better document purpose & usage
+        // Manually Launch a synchronization between keycloack and the SCIM endpoint described in the given model
         LOGGER.infof("[SCIM] Sync from ScimStorageProvider - Realm %s - Model %s", realmId, model.getId());
         SynchronizationResult result = new SynchronizationResult();
         KeycloakModelUtils.runJobInTransaction(sessionFactory, session -> {
@@ -68,25 +64,8 @@ public class ScimEndpointConfigurationStorageProviderFactory
 
     @Override
     public void postInit(KeycloakSessionFactory factory) {
-        // TODO : find a better way to handle scim dirty (use a QUEUE for SCIM queries ?)
-        try (KeycloakSession keycloakSession = factory.create()) {
-            TimerProvider timer = keycloakSession.getProvider(TimerProvider.class);
-            timer.scheduleTask(taskSession -> {
-                for (RealmModel realm : taskSession.realms().getRealmsStream().toList()) {
-                    KeycloakModelUtils.runJobInTransaction(factory, session -> {
-                        session.getContext().setRealm(realm);
-                        ScimDispatcher dispatcher = new ScimDispatcher(session);
-                        for (GroupModel group : session.groups().getGroupsStream(realm)
-                                .filter(x -> BooleanUtils.TRUE.equals(x.getFirstAttribute("scim-dirty"))).toList()) {
-                            LOGGER.infof("[SCIM] Dirty group: %s", group.getName());
-                            dispatcher.dispatchGroupModificationToAll(client -> client.update(group));
-                            group.removeAttribute("scim-dirty");
-                        }
-                        dispatcher.close();
-                    });
-                }
-            }, Duration.ofSeconds(30).toMillis(), "scim-background");
-        }
+        ScimBackgroundGroupMembershipUpdater scimBackgroundGroupMembershipUpdater = new ScimBackgroundGroupMembershipUpdater(factory);
+        scimBackgroundGroupMembershipUpdater.startBackgroundUpdates();
     }
 
     @Override
diff --git a/src/main/java/sh/libre/scim/core/exceptions/RollbackApproach.java b/src/main/java/sh/libre/scim/core/exceptions/RollbackApproach.java
index fa5f5983dd18dc89f5afd34165e18fcff9d8f181..aeca5e930c09e0dba3af6b48bf363a00b88ba604 100644
--- a/src/main/java/sh/libre/scim/core/exceptions/RollbackApproach.java
+++ b/src/main/java/sh/libre/scim/core/exceptions/RollbackApproach.java
@@ -1,7 +1,10 @@
 package sh.libre.scim.core.exceptions;
 
+import com.google.common.collect.Lists;
 import sh.libre.scim.core.ScrimEndPointConfiguration;
 
+import java.util.ArrayList;
+
 
 public enum RollbackApproach implements RollbackStrategy {
     ALWAYS_ROLLBACK {
@@ -37,8 +40,10 @@ public enum RollbackApproach implements RollbackStrategy {
         }
 
         private boolean shouldRollbackBecauseOfResponse(InvalidResponseFromScimEndpointException e) {
+            // We consider that 404 are acceptable, otherwise rollback
             int httpStatus = e.getResponse().getHttpStatus();
-            return httpStatus == 500;
+            ArrayList<Integer> acceptableStatus = Lists.newArrayList(200, 204, 404);
+            return !acceptableStatus.contains(httpStatus);
         }
     }
 }
diff --git a/src/main/java/sh/libre/scim/core/AbstractScimService.java b/src/main/java/sh/libre/scim/core/service/AbstractScimService.java
similarity index 99%
rename from src/main/java/sh/libre/scim/core/AbstractScimService.java
rename to src/main/java/sh/libre/scim/core/service/AbstractScimService.java
index c350702911fb1230199cd718a6cf8b37ca8444d6..8397464a01b955875f7e540682d0826d69f63a78 100644
--- a/src/main/java/sh/libre/scim/core/AbstractScimService.java
+++ b/src/main/java/sh/libre/scim/core/service/AbstractScimService.java
@@ -1,4 +1,4 @@
-package sh.libre.scim.core;
+package sh.libre.scim.core.service;
 
 import de.captaingoldfish.scim.sdk.common.resources.ResourceNode;
 import de.captaingoldfish.scim.sdk.common.resources.complex.Meta;
@@ -6,6 +6,7 @@ import org.jboss.logging.Logger;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RoleMapperModel;
 import org.keycloak.storage.user.SynchronizationResult;
+import sh.libre.scim.core.ScrimEndPointConfiguration;
 import sh.libre.scim.core.exceptions.InconsistentScimMappingException;
 import sh.libre.scim.core.exceptions.InvalidResponseFromScimEndpointException;
 import sh.libre.scim.core.exceptions.SkipOrStopStrategy;
diff --git a/src/main/java/sh/libre/scim/core/EntityOnRemoteScimId.java b/src/main/java/sh/libre/scim/core/service/EntityOnRemoteScimId.java
similarity index 65%
rename from src/main/java/sh/libre/scim/core/EntityOnRemoteScimId.java
rename to src/main/java/sh/libre/scim/core/service/EntityOnRemoteScimId.java
index 3249608baa3a9f997d8760f805ce77df04fb6b9c..df96a12323c933b683631b252b1b6529e94c5c5a 100644
--- a/src/main/java/sh/libre/scim/core/EntityOnRemoteScimId.java
+++ b/src/main/java/sh/libre/scim/core/service/EntityOnRemoteScimId.java
@@ -1,4 +1,4 @@
-package sh.libre.scim.core;
+package sh.libre.scim.core.service;
 
 public record EntityOnRemoteScimId(
         String asString
diff --git a/src/main/java/sh/libre/scim/core/GroupScimService.java b/src/main/java/sh/libre/scim/core/service/GroupScimService.java
similarity index 98%
rename from src/main/java/sh/libre/scim/core/GroupScimService.java
rename to src/main/java/sh/libre/scim/core/service/GroupScimService.java
index 35daece5188a8513bdd8a7fda8cc15317490d07b..5297acdac350b678e58e24f3893b27af8f1d5334 100644
--- a/src/main/java/sh/libre/scim/core/GroupScimService.java
+++ b/src/main/java/sh/libre/scim/core/service/GroupScimService.java
@@ -1,4 +1,4 @@
-package sh.libre.scim.core;
+package sh.libre.scim.core.service;
 
 import de.captaingoldfish.scim.sdk.common.resources.Group;
 import de.captaingoldfish.scim.sdk.common.resources.complex.Meta;
@@ -10,6 +10,7 @@ import org.jboss.logging.Logger;
 import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.UserModel;
+import sh.libre.scim.core.ScrimEndPointConfiguration;
 import sh.libre.scim.core.exceptions.InconsistentScimMappingException;
 import sh.libre.scim.core.exceptions.SkipOrStopStrategy;
 import sh.libre.scim.core.exceptions.UnexpectedScimDataException;
diff --git a/src/main/java/sh/libre/scim/core/KeycloakDao.java b/src/main/java/sh/libre/scim/core/service/KeycloakDao.java
similarity index 98%
rename from src/main/java/sh/libre/scim/core/KeycloakDao.java
rename to src/main/java/sh/libre/scim/core/service/KeycloakDao.java
index 67f58da9f9ad5091190def3b4d9d5f163a99a82a..f4c406c351300660e86ef3e423515cc018924425 100644
--- a/src/main/java/sh/libre/scim/core/KeycloakDao.java
+++ b/src/main/java/sh/libre/scim/core/service/KeycloakDao.java
@@ -1,4 +1,4 @@
-package sh.libre.scim.core;
+package sh.libre.scim.core.service;
 
 import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
diff --git a/src/main/java/sh/libre/scim/core/KeycloakId.java b/src/main/java/sh/libre/scim/core/service/KeycloakId.java
similarity index 54%
rename from src/main/java/sh/libre/scim/core/KeycloakId.java
rename to src/main/java/sh/libre/scim/core/service/KeycloakId.java
index 432892e6f1cd4940682a0302daef12b644b75789..04bad470b3b40f6178ffde70b15a6e3f253f1d53 100644
--- a/src/main/java/sh/libre/scim/core/KeycloakId.java
+++ b/src/main/java/sh/libre/scim/core/service/KeycloakId.java
@@ -1,6 +1,5 @@
-package sh.libre.scim.core;
+package sh.libre.scim.core.service;
 
-// TODO rename this
 public record KeycloakId(
         String asString
 ) {
diff --git a/src/main/java/sh/libre/scim/core/ScimClient.java b/src/main/java/sh/libre/scim/core/service/ScimClient.java
similarity index 95%
rename from src/main/java/sh/libre/scim/core/ScimClient.java
rename to src/main/java/sh/libre/scim/core/service/ScimClient.java
index 7649ccb03cb379c1239e932f8c89a3ba30b7de56..7fcda9009173a7813e1236b1d15de0f32006b5cf 100644
--- a/src/main/java/sh/libre/scim/core/ScimClient.java
+++ b/src/main/java/sh/libre/scim/core/service/ScimClient.java
@@ -1,4 +1,4 @@
-package sh.libre.scim.core;
+package sh.libre.scim.core.service;
 
 import com.google.common.net.HttpHeaders;
 import de.captaingoldfish.scim.sdk.client.ScimClientConfig;
@@ -12,9 +12,9 @@ import io.github.resilience4j.retry.RetryConfig;
 import io.github.resilience4j.retry.RetryRegistry;
 import jakarta.ws.rs.ProcessingException;
 import org.jboss.logging.Logger;
+import sh.libre.scim.core.ScrimEndPointConfiguration;
 import sh.libre.scim.core.exceptions.InvalidResponseFromScimEndpointException;
 
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -53,8 +53,6 @@ public class ScimClient<S extends ResourceNode> implements AutoCloseable {
                 .connectTimeout(5)
                 .requestTimeout(5)
                 .socketTimeout(5)
-                .expectedHttpResponseHeaders(Collections.emptyMap()) // strange, useful?
-                // TODO Question Indiehoster : should we really allow connection with TLS ? .hostnameVerifier((s, sslSession) -> true)
                 .build();
         ScimRequestBuilder scimRequestBuilder =
                 new ScimRequestBuilder(
diff --git a/src/main/java/sh/libre/scim/core/ScimResourceType.java b/src/main/java/sh/libre/scim/core/service/ScimResourceType.java
similarity index 95%
rename from src/main/java/sh/libre/scim/core/ScimResourceType.java
rename to src/main/java/sh/libre/scim/core/service/ScimResourceType.java
index 23df9f8c881f4d93c3bb30d3e2fcf23e4e932807..b90845b6cfd63852c41218e40222a24a639cf13b 100644
--- a/src/main/java/sh/libre/scim/core/ScimResourceType.java
+++ b/src/main/java/sh/libre/scim/core/service/ScimResourceType.java
@@ -1,4 +1,4 @@
-package sh.libre.scim.core;
+package sh.libre.scim.core.service;
 
 import de.captaingoldfish.scim.sdk.common.resources.Group;
 import de.captaingoldfish.scim.sdk.common.resources.ResourceNode;
diff --git a/src/main/java/sh/libre/scim/core/UserScimService.java b/src/main/java/sh/libre/scim/core/service/UserScimService.java
similarity index 98%
rename from src/main/java/sh/libre/scim/core/UserScimService.java
rename to src/main/java/sh/libre/scim/core/service/UserScimService.java
index 54c8345a20cbd06bc607be6938ed22fef0e42ca4..cfd4a826ada5a9da1c5c71cb54b865d7be41c640 100644
--- a/src/main/java/sh/libre/scim/core/UserScimService.java
+++ b/src/main/java/sh/libre/scim/core/service/UserScimService.java
@@ -1,4 +1,4 @@
-package sh.libre.scim.core;
+package sh.libre.scim.core.service;
 
 import de.captaingoldfish.scim.sdk.common.resources.User;
 import de.captaingoldfish.scim.sdk.common.resources.complex.Meta;
@@ -13,6 +13,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RoleMapperModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
+import sh.libre.scim.core.ScrimEndPointConfiguration;
 import sh.libre.scim.core.exceptions.InconsistentScimMappingException;
 import sh.libre.scim.core.exceptions.SkipOrStopStrategy;
 import sh.libre.scim.core.exceptions.UnexpectedScimDataException;
diff --git a/src/main/java/sh/libre/scim/event/ScimBackgroundGroupMembershipUpdater.java b/src/main/java/sh/libre/scim/event/ScimBackgroundGroupMembershipUpdater.java
new file mode 100644
index 0000000000000000000000000000000000000000..d7a0731573135672a450d48ec3b850309372aed4
--- /dev/null
+++ b/src/main/java/sh/libre/scim/event/ScimBackgroundGroupMembershipUpdater.java
@@ -0,0 +1,74 @@
+package sh.libre.scim.event;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.timer.TimerProvider;
+import sh.libre.scim.core.ScimDispatcher;
+
+import java.time.Duration;
+
+/**
+ * In charge of making background checks and sent
+ * UPDATE requests from group for which membership information has changed.
+ * <p>
+ * This is required to avoid immediate group membership updates which could cause
+ * to incorrect group members list in case of concurrent group membership changes.
+ */
+public class ScimBackgroundGroupMembershipUpdater {
+    public static final String GROUP_DIRTY_SINCE_ATTRIBUTE_NAME = "scim-dirty-since";
+
+    private static final Logger LOGGER = Logger.getLogger(ScimBackgroundGroupMembershipUpdater.class);
+    // Update check loop will run every time this delay has passed
+    private static final long UPDATE_CHECK_DELAY_MS = 2000;
+    // If a group is marked dirty since less that this debounce delay, wait for the next update check loop
+    private static final long DEBOUNCE_DELAY_MS = 1200;
+    private final KeycloakSessionFactory sessionFactory;
+
+    public ScimBackgroundGroupMembershipUpdater(KeycloakSessionFactory sessionFactory) {
+        this.sessionFactory = sessionFactory;
+    }
+
+    public void startBackgroundUpdates() {
+        // Every UPDATE_CHECK_DELAY_MS, check for dirty groups and send updates if required
+        try (KeycloakSession keycloakSession = sessionFactory.create()) {
+            TimerProvider timer = keycloakSession.getProvider(TimerProvider.class);
+            timer.scheduleTask(taskSession -> {
+                for (RealmModel realm : taskSession.realms().getRealmsStream().toList()) {
+                    dispatchDirtyGroupsUpdates(realm);
+                }
+            }, Duration.ofMillis(UPDATE_CHECK_DELAY_MS).toMillis(), "scim-background");
+        }
+    }
+
+    private void dispatchDirtyGroupsUpdates(RealmModel realm) {
+        KeycloakModelUtils.runJobInTransaction(sessionFactory, session -> {
+            session.getContext().setRealm(realm);
+            ScimDispatcher dispatcher = new ScimDispatcher(session);
+            // Identify groups marked as dirty by the ScimEventListenerProvider
+            for (GroupModel group : session.groups().getGroupsStream(realm)
+                    .filter(this::isDirtyGroup).toList()) {
+                LOGGER.infof("[SCIM] Group %s is dirty, dispatch an update", group.getName());
+                // If dirty : dispatch a group update to all clients and mark it clean
+                dispatcher.dispatchGroupModificationToAll(client -> client.update(group));
+                group.removeAttribute(GROUP_DIRTY_SINCE_ATTRIBUTE_NAME);
+            }
+            dispatcher.close();
+        });
+    }
+
+    private boolean isDirtyGroup(GroupModel g) {
+        String groupDirtySinceAttribute = g.getFirstAttribute(GROUP_DIRTY_SINCE_ATTRIBUTE_NAME);
+        try {
+            int groupDirtySince = Integer.parseInt(groupDirtySinceAttribute);
+            // Must be dirty for more than DEBOUNCE_DELAY_MS
+            // (otherwise update will be dispatched in next scheduled loop)
+            return System.currentTimeMillis() - groupDirtySince > DEBOUNCE_DELAY_MS;
+        } catch (NumberFormatException e) {
+            return false;
+        }
+    }
+}
diff --git a/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java b/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java
index 4d6f7f470f5e2d0759e75340fbbaa77609084da4..7d765b6004eb4d0d08c24d931776cac1a47ed453 100644
--- a/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java
+++ b/src/main/java/sh/libre/scim/event/ScimEventListenerProvider.java
@@ -1,6 +1,5 @@
 package sh.libre.scim.event;
 
-import org.apache.commons.lang3.BooleanUtils;
 import org.jboss.logging.Logger;
 import org.keycloak.events.Event;
 import org.keycloak.events.EventListenerProvider;
@@ -11,10 +10,10 @@ import org.keycloak.events.admin.ResourceType;
 import org.keycloak.models.GroupModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.UserModel;
-import sh.libre.scim.core.KeycloakDao;
-import sh.libre.scim.core.KeycloakId;
 import sh.libre.scim.core.ScimDispatcher;
-import sh.libre.scim.core.ScimResourceType;
+import sh.libre.scim.core.service.KeycloakDao;
+import sh.libre.scim.core.service.KeycloakId;
+import sh.libre.scim.core.service.ScimResourceType;
 
 import java.util.Map;
 import java.util.regex.Matcher;
@@ -35,7 +34,7 @@ public class ScimEventListenerProvider implements EventListenerProvider {
 
     private final KeycloakDao keycloakDao;
 
-    private final Map<ResourceType, Pattern> patterns = Map.of(
+    private final Map<ResourceType, Pattern> listenedEventPathPatterns = Map.of(
             ResourceType.USER, Pattern.compile("users/(.+)"),
             ResourceType.GROUP, Pattern.compile("groups/([\\w-]+)(/children)?"),
             ResourceType.GROUP_MEMBERSHIP, Pattern.compile("users/(.+)/groups/(.+)"),
@@ -79,7 +78,7 @@ public class ScimEventListenerProvider implements EventListenerProvider {
     @Override
     public void onEvent(AdminEvent event, boolean includeRepresentation) {
         // Step 1: check if event is relevant for propagation through SCIM
-        Pattern pattern = patterns.get(event.getResourceType());
+        Pattern pattern = listenedEventPathPatterns.get(event.getResourceType());
         if (pattern == null)
             return;
         Matcher matcher = pattern.matcher(event.getResourcePath());
@@ -171,10 +170,16 @@ public class ScimEventListenerProvider implements EventListenerProvider {
 
     private void handleGroupMemberShipEvent(AdminEvent groupMemberShipEvent, KeycloakId userId, KeycloakId groupId) {
         LOGGER.infof("[SCIM] Propagate GroupMemberShip %s - User %s Group %s", groupMemberShipEvent.getOperationType(), userId, groupId);
+        // Step 1: update USER immediately
         GroupModel group = getGroup(groupId);
-        group.setSingleAttribute("scim-dirty", BooleanUtils.TRUE);
         UserModel user = getUser(userId);
         dispatcher.dispatchUserModificationToAll(client -> client.update(user));
+
+        // Step 2: delayed GROUP update :
+        // if several users are added to the group simultaneously in different Keycloack sessions
+        // update the group in the context of the current session may not reflect those other changes
+        // We trigger a delayed update by setting an attribute on the group (that will be handled by ScimBackgroundGroupMembershipUpdaters)
+        group.setSingleAttribute(ScimBackgroundGroupMembershipUpdater.GROUP_DIRTY_SINCE_ATTRIBUTE_NAME, "" + System.currentTimeMillis());
     }
 
     private void handleRoleMappingEvent(AdminEvent roleMappingEvent, ScimResourceType type, KeycloakId id) {
diff --git a/src/main/java/sh/libre/scim/jpa/ScimResourceDao.java b/src/main/java/sh/libre/scim/jpa/ScimResourceDao.java
index 97db33b9ea3caf8dad20e7112420481c495c775e..3824ad44d89ebfc014584963ea490af1863cc1c2 100644
--- a/src/main/java/sh/libre/scim/jpa/ScimResourceDao.java
+++ b/src/main/java/sh/libre/scim/jpa/ScimResourceDao.java
@@ -5,9 +5,9 @@ import jakarta.persistence.NoResultException;
 import jakarta.persistence.TypedQuery;
 import org.keycloak.connections.jpa.JpaConnectionProvider;
 import org.keycloak.models.KeycloakSession;
-import sh.libre.scim.core.EntityOnRemoteScimId;
-import sh.libre.scim.core.KeycloakId;
-import sh.libre.scim.core.ScimResourceType;
+import sh.libre.scim.core.service.EntityOnRemoteScimId;
+import sh.libre.scim.core.service.KeycloakId;
+import sh.libre.scim.core.service.ScimResourceType;
 
 import java.util.Optional;
 
diff --git a/src/main/java/sh/libre/scim/jpa/ScimResourceId.java b/src/main/java/sh/libre/scim/jpa/ScimResourceId.java
index 99bce7673edec14240cfb0f94f7b3247b2a50eb7..d0abddf2b1bf9c93c473f94af1dbf061afbb92f6 100644
--- a/src/main/java/sh/libre/scim/jpa/ScimResourceId.java
+++ b/src/main/java/sh/libre/scim/jpa/ScimResourceId.java
@@ -67,10 +67,8 @@ public class ScimResourceId implements Serializable {
     public boolean equals(Object other) {
         if (this == other)
             return true;
-        if (!(other instanceof ScimResourceId))
+        if (!(other instanceof ScimResourceId o))
             return false;
-        ScimResourceId o = (ScimResourceId) other;
-        // TODO
         return (StringUtils.equals(o.id, id) &&
                 StringUtils.equals(o.realmId, realmId) &&
                 StringUtils.equals(o.componentId, componentId) &&
diff --git a/src/main/java/sh/libre/scim/jpa/ScimResourceMapping.java b/src/main/java/sh/libre/scim/jpa/ScimResourceMapping.java
index 26eafa04c2315b5bef0b60309d3b04ca71821f50..fabd266444cff6e9fe235658f293a270254688fa 100644
--- a/src/main/java/sh/libre/scim/jpa/ScimResourceMapping.java
+++ b/src/main/java/sh/libre/scim/jpa/ScimResourceMapping.java
@@ -7,8 +7,8 @@ import jakarta.persistence.IdClass;
 import jakarta.persistence.NamedQueries;
 import jakarta.persistence.NamedQuery;
 import jakarta.persistence.Table;
-import sh.libre.scim.core.EntityOnRemoteScimId;
-import sh.libre.scim.core.KeycloakId;
+import sh.libre.scim.core.service.EntityOnRemoteScimId;
+import sh.libre.scim.core.service.KeycloakId;
 
 @Entity
 @IdClass(ScimResourceId.class)
diff --git a/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory b/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory
index 23371ddf7c38517c3a2e0f5713783e90c27ea10a..308796c862e2aa83c0aa9212cb123128a749bd1e 100644
--- a/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory
+++ b/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory
@@ -1 +1 @@
-sh.libre.scim.storage.ScimEndpointConfigurationStorageProviderFactory
+sh.libre.scim.core.ScimEndpointConfigurationStorageProviderFactory