Thanks Francesco ! I've modified my code, but now I'm getting NullPointerException (on the second PUT request on /Group endpoint).This error isn't generated when we execute the request serially.It's generated when HTTP request are executed in parallel.
i.e. PUT http://localhost:9091/groups/2564b3a7-0f5d-424d-a4b3-a70f5d624d80 => gives a 200PUT http://localhost:9091/groups/fad94db3-9245-449e-994d-b39245449e6a => gives a 500 My code is available in [1] The stacktrace is available in [2] The error in syncope is from JPAUserDAO (input is null) :public Collection<Group> findAllGroups(final User user) { return CollectionUtils.union( CollectionUtils.collect(user.getMemberships(), new Transformer<UMembership, Group>() { @Override public Group transform(final UMembership input) { return input.getRightEnd(); } }, new ArrayList<Group>()), findDynGroupMemberships(user)); } [1] Here's exactly the code from the SCIM Provider side (calling Syncope REST API) : // This is the method called on PUT for Group endpoint public Group update(String id, Group group) { // 1. General group update GroupTO existingGroupTO = getGroupTOByKey(id); List<UserTO> usersOfThisGroup = getUsersForGroup(id); GroupTO groupTO = groupConverter.fromScim(group); groupTO.setKey(existingGroupTO.getKey()); GroupService groupService = syncopeClient.getService(GroupService.class); Response response; try { response = groupService.update(groupTO); } catch (SyncopeClientException ex) { throw syncopeExHandler.convertSyncopeGroupClientException(ex, group.getDisplayName(), ProvisioningOperation.UPDATING); } if (response == null || Response.Status.OK.getStatusCode() != response.getStatus()) { throw syncopeExHandler.convertSyncopeGroupErrorResponse(response, group.getDisplayName(), ProvisioningOperation.UPDATING); } // 2. General membership update List<String> idsOfExistingUsers = usersOfThisGroup.stream().map(user -> user.getKey()).collect(Collectors.toList()); List<String> idsOfNewUsers = group.getMembers().stream().map(member -> member.getValue()).collect(Collectors.toList()); for (MemberRef memberRef : group.getMembers()) { if (!idsOfExistingUsers.contains(memberRef.getValue())) { addUserToGroup(groupTO, memberRef.getValue()); } } for (String userId : idsOfExistingUsers) { if (!idsOfNewUsers.contains(userId)) { removeUserFromGroup(groupTO, userId); } } return getById(id); } private void addUserToGroup(GroupTO groupTO, String userId) { UserService userService = syncopeClient.getService(UserService.class); UserPatch userPatch = new UserPatch(); userPatch.setKey(userId); userPatch.getMemberships().add( new MembershipPatch.Builder().operation(PatchOperation.ADD_REPLACE).group(groupTO.getKey()).build()); try { userService.update(userPatch); } catch (SyncopeClientException e) { throw new SCIMException(String.format("User %s was not added to the group %s", userId, groupTO.getName()), e); } } private void removeUserFromGroup(GroupTO groupTO, String userId) { UserService userService = syncopeClient.getService(UserService.class); UserPatch userPatch = new UserPatch(); userPatch.setKey(userId); userPatch.getMemberships().add( new MembershipPatch.Builder().operation(PatchOperation.DELETE).group(groupTO.getKey()).build()); try { userService.update(userPatch); } catch (SyncopeClientException e) { throw new SCIMException(String.format("User %s was not removed from the group %s", userId, groupTO.getName()), e); } } [2] Stacktrace Sorry for the super long stack : 15:05:47.623 ERROR org.apache.syncope.core.rest.cxf.RestServiceExceptionMapper - Exception thrown java.lang.NullPointerException at org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO$2.transform(JPAUserDAO.java:500) ~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO$2.transform(JPAUserDAO.java:496) ~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at org.apache.commons.collections4.CollectionUtils.collect(CollectionUtils.java:1077) ~[commons-collections4-4.1.jar:4.1] at org.apache.commons.collections4.CollectionUtils.collect(CollectionUtils.java:1049) ~[commons-collections4-4.1.jar:4.1] at org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO.findAllGroups(JPAUserDAO.java:495) ~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO.findAllResources(JPAUserDAO.java:529) ~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at sun.reflect.GeneratedMethodAccessor124.invoke(Unknown Source) ~[?:?] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_91] at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_91] at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) ~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:280) ~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE] ] at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE--More-- at org.apache.syncope.core.persistence.jpa.spring.DomainTransactionInterceptor.invoke(DomainTransactionInterceptor.java:64) ~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at com.sun.proxy.$Proxy172.findAllResources(Unknown Source) ~[?:?] at org.apache.syncope.core.provisioning.java.propagation.PropagationManagerImpl.createTasks(PropagationManagerImpl.java:345) ~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at org.apache.syncope.core.provisioning.java.propagation.PropagationManagerImpl.getUpdateTasks(PropagationManagerImpl.java:275) ~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at org.apache.syncope.core.provisioning.java.propagation.PropagationManagerImpl.getUserUpdateTasks(PropagationManagerImpl.java:204) ~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at org.apache.syncope.core.provisioning.java.propagation.PropagationManagerImpl.getUserUpdateTasks(PropagationManagerImpl.java:224) ~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] _91] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0--More-- at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_91] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_91] at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_91] at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:333) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) ~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:280) ~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.apache.syncope.core.persistence.jpa.spring.DomainTransactionInterceptor.invoke(DomainTransactionInterceptor.java:64) ~[syncope-core-persistence-jpa-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at com.sun.proxy.$Proxy209.getUserUpdateTasks(Unknown Source) ~[?:?] at org.apache.syncope.core.provisioning.java.DefaultUserProvisioningManager.update(DefaultUserProvisioningManager.java:123) ~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at org.apache.syncope.core.provisioning.java.DefaultUserProvisioningManager.update(DefaultUserProvisioningManager.java:57) ~[syncope-core-provisioning-java-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at org.apache.syncope.core.logic.UserLogic.doUpdate(UserLogic.java:232) ~[syncope-core-logic-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at org.apache.syncope.core.logic.UserLogic.update(UserLogic.java:213) ~[syncope-core-logic-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at org.apache.syncope.core.logic.UserLogic.update(UserLogic.java:68) ~[syncope-core-logic-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-4.3.2.RELEASE.jar:4.3.2.RELEASE]0-SNAPSHOT.jar:2.0.0-SNAPSHOT]--More-- at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.apache.syncope.core.logic.LogicInvocationHandler.around(LogicInvocationHandler.java:72) ~[syncope-core-logic-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at sun.reflect.GeneratedMethodAccessor133.invoke(Unknown Source) ~[?:?] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_91] at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_91] at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:618) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:69) ~[spring-security-core-4.1.3.RELEASE.jar:4.1.3.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.apache.syncope.core.logic.UserLogic$$EnhancerBySpringCGLIB$$f6ff0886.update(<generated>) ~[syncope-core-logic-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] stractAnyService.java:159) ~[syncope-core-rest-cxf-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT]e-- at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_91] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_91] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_91] at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_91] at org.apache.cxf.service.invoker.AbstractInvoker.performInvocation(AbstractInvoker.java:180) ~[cxf-core-3.1.7.jar:3.1.7] at org.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:96) ~[cxf-core-3.1.7.jar:3.1.7] at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:189) ~[cxf-rt-frontend-jaxrs-3.1.7.jar:3.1.7] at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:99) ~[cxf-rt-frontend-jaxrs-3.1.7.jar:3.1.7] at org.apache.cxf.interceptor.ServiceInvokerInterceptor$1.run(ServiceInvokerInterceptor.java:59) ~[cxf-core-3.1.7.jar:3.1.7] at org.apache.cxf.interceptor.ServiceInvokerInterceptor.handleMessage(ServiceInvokerInterceptor.java:96) ~[cxf-core-3.1.7.jar:3.1.7] at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308) ~[cxf-core-3.1.7.jar:3.1.7] at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:121) ~[cxf-core-3.1.7.jar:3.1.7] at org.apache.cxf.transport.http.AbstractHTTPDestination.invoke(AbstractHTTPDestination.java:254) ~[cxf-rt-transports-http-3.1.7.jar:3.1.7] at org.apache.cxf.transport.servlet.ServletController.invokeDestination(ServletController.java:234) ~[cxf-rt-transports-http-3.1.7.jar:3.1.7] at org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:208) ~[cxf-rt-transports-http-3.1.7.jar:3.1.7] at org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:160) ~[cxf-rt-transports-http-3.1.7.jar:3.1.7] at org.apache.cxf.transport.servlet.CXFNonSpringServlet.invoke(CXFNonSpringServlet.java:180) ~[cxf-rt-transports-http-3.1.7.jar:3.1.7] at org.apache.cxf.transport.servlet.AbstractHTTPServlet.handleRequest(AbstractHTTPServlet.java:299) ~[cxf-rt-transports-http-3.1.7.jar:3.1.7] at org.apache.cxf.transport.servlet.AbstractHTTPServlet.service(AbstractHTTPServlet.java:276) ~[cxf-rt-transports-http-3.1.7.jar:3.1.7] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292) ~[catalina.jar:8.0.35] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) ~[catalina.jar:8.0.35] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) ~[tomcat-websocket.jar:8.0.35] icationFilterChain.java:240) ~[catalina.jar:8.0.35]erChain.internalDoFilter(Appl--More-- at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207) ~[catalina.jar:8.0.35] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:317) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE] at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:127) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE] at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE] at org.apache.syncope.core.spring.security.MustChangePasswordFilter.doFilter(MustChangePasswordFilter.java:77) ~[syncope-core-spring-2.0.0-SNAPSHOT.jar:2.0.0-SNAPSHOT] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE] jar:4.1.3.RELEASE]ingframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:115) ~[spring-security-web-4.1.3.RELEASE.--More-- at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE] at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE] at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE] at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:169) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE] .doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE] at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilterInternal(BasicAuthenticationFilter.java:215) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]http://localhost:9091/groups/fad94db3-9245-449e-994d-b39245449e De : Francesco Chicchiriccò <ilgro...@apache.org> À : user@syncope.apache.org Envoyé le : Mardi 27 septembre 2016 15h20 Objet : Re: SCIM & Syncope : OptimisticLockException on user groups membership update On 27/09/2016 15:00, Adrian Gonzalez wrote: Hello, We're trying to build a POC on SCIM APIs on top of Syncope. That's very good to hear: looks it is for SCIM 2.0, correct? Looking forward to take a look at it! Problem is when we're using some basic SCIM APIs to update the groups membership of a given user, we got a OptimisticLockException. This is due to the fact that SCIM group membership can be updated only from the Group endpoint (not from the User endpoint). From https://tools.ietf.org/html/rfc7643#page-24 groups A list of groups to which the user belongs,... Since this attribute has a mutability of "readOnly", group membership changes MUST be applied via the "Group" Resource (Section 4.2). This attribute has a mutability of "readOnly". So, we have the following scenario : * we have a user1 member of 2 groups (group1 and group2). * we want to remove the user1 from both groups from a UI console. * the UI then needs to send 2 HTTP PUT on SCIM /Groups endpoint (one for each group). /PUT Groups/group1 /PUT Groups/group2 * we get a OptimisticLockException since both calls are made for a relation on the same user - because on the SCIM side for the Group endpoint, we must call userService.update(userTO) to update a user <-> group relation. i.e. MembershipTO membershipTO = new MembershipTO.Builder().group(userTO.getKey(), "USER").group(groupTO.getKey(), groupTO.getName()).build(); userTO.getMemberships().add(membershipTO); try { userService.update(userTO); } catch (SyncopeClientException e) { throw new SCIMException(String.format("User %s was not added to the group %s", userId, groupTO.getName()), e); } Is there an API to update user or group membership without testing @Version field ? (i.e like a syncope REST API on top of a jpql update ?) Perhaps we're not using the good API here (is there an API to handle membership from the group's side ? Do you see another possible solution (besides updating membership from the Group side/screen) ? You are right about the fact that Syncope manages groups memberships by modifying users. I am a bit confused about your snippet above, so let me rephrase it a bit. Assuming your scenario: * we have a user1 member of 2 groups (group1 and group2). * we want to remove the user1 from both groups from a UI console. * the UI then needs to send 2 HTTP PUT on SCIM /Groups endpoint (one for each group). /PUT Groups/group1 /PUT Groups/group2 the Group endpoint should perform as following: UserPatch userPatch = new UserPatch(); userPatch.setKey("the key of user1"); userPatch.getMemberships().add(new MembershipPatch.Builder(). operation(PatchOperation.DELETE). group("the key of the group passed to the endpoint"). build()); userLogic.update(userPatch, true); As you can see, I am rather using the logic layer (e.g. UserLogic as done in UserServiceImpl) to interact with Syncope core, rather than the external service layer (e.g. how REST clients do). HTH Regards. -- Francesco Chicchiriccò Tirasa - Open Source Excellence http://www.tirasa.net/ Member at The Apache Software Foundation Syncope, Cocoon, Olingo, CXF, OpenJPA, PonyMail http://home.apache.org/~ilgrosso/