You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by bd...@apache.org on 2023/11/13 17:05:28 UTC

(directory-scimple) branch update-patch-attributes created (now 09e1889a)

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

bdemers pushed a change to branch update-patch-attributes
in repository https://gitbox.apache.org/repos/asf/directory-scimple.git


      at 09e1889a Move attribute and etag logic into Repository

This branch includes the following new commits:

     new 09e1889a Move attribute and etag logic into Repository

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(directory-scimple) 01/01: Move attribute and etag logic into Repository

Posted by bd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

bdemers pushed a commit to branch update-patch-attributes
in repository https://gitbox.apache.org/repos/asf/directory-scimple.git

commit 09e1889a36208b45f2e67eccb6f5902a8566abc8
Author: Brian Demers <bd...@apache.org>
AuthorDate: Mon Nov 13 12:05:17 2023 -0500

    Move attribute and etag logic into Repository
    
    Breaks up the current `update` method into new `update` and `patch` methods.
    (Breaking change)
    
    - ETag/Version logic must be managed by Repositories, except for the case of `ServiceProviderConfiguration` (which is likely stored in memory)
      - Implementations should set `scimResource.meta.version`, how this version is defined is up to the implementation
    - The `UpdateRequest` class has been removed, the `getPatchOperations` method has been moved into a new class `PatchGenerator`
    - Current examples have been updated and demo simple in memory patch application. e.g. Get/apply patch/persist.
      - Other implementations may want generate update queries directly from the list of PatchOperations (skipping intermediate get step)
---
 .../scim/core/repository/PatchGenerator.java       | 523 ++++++++++++++++++
 .../directory/scim/core/repository/Repository.java |  32 +-
 .../scim/core/repository/UpdateRequest.java        | 589 ---------------------
 ...ateRequestTest.java => PatchGeneratorTest.java} | 124 ++---
 .../jersey/service/InMemoryGroupService.java       |  20 +-
 .../jersey/service/InMemoryUserService.java        |  24 +-
 .../memory/service/InMemoryGroupService.java       |  20 +-
 .../memory/service/InMemoryUserService.java        |  23 +-
 .../quarkus/service/InMemoryGroupService.java      |  20 +-
 .../quarkus/service/InMemoryUserService.java       |  27 +-
 .../spring/service/InMemoryGroupService.java       |  20 +-
 .../spring/service/InMemoryUserService.java        |  23 +-
 .../server/rest/BaseResourceTypeResourceImpl.java  | 126 +++--
 .../scim/server/rest/BulkResourceImpl.java         |  12 +-
 .../scim/server/rest/GroupResourceImpl.java        |   6 +-
 .../scim/server/rest/UserResourceImpl.java         |   6 +-
 .../server/it/testapp/InMemoryGroupService.java    |  25 +-
 .../server/it/testapp/InMemoryUserService.java     |  23 +-
 .../scim/spring/it/app/InMemoryGroupService.java   |  20 +-
 .../scim/spring/it/app/InMemoryUserService.java    |  23 +-
 20 files changed, 836 insertions(+), 850 deletions(-)

diff --git a/scim-core/src/main/java/org/apache/directory/scim/core/repository/PatchGenerator.java b/scim-core/src/main/java/org/apache/directory/scim/core/repository/PatchGenerator.java
new file mode 100644
index 00000000..1b6c7473
--- /dev/null
+++ b/scim-core/src/main/java/org/apache/directory/scim/core/repository/PatchGenerator.java
@@ -0,0 +1,523 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+ 
+* http://www.apache.org/licenses/LICENSE-2.0
+
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied.  See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+package org.apache.directory.scim.core.repository;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.*;
+import com.flipkart.zjsonpatch.DiffFlags;
+import com.flipkart.zjsonpatch.JsonDiff;
+import org.apache.directory.scim.core.json.ObjectMapperFactory;
+import org.apache.directory.scim.core.schema.SchemaRegistry;
+import org.apache.directory.scim.spec.filter.AttributeComparisonExpression;
+import org.apache.directory.scim.spec.filter.CompareOperator;
+import org.apache.directory.scim.spec.filter.FilterExpression;
+import org.apache.directory.scim.spec.filter.ValuePathExpression;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
+import org.apache.directory.scim.spec.patch.PatchOperationPath;
+import org.apache.directory.scim.spec.resources.ScimExtension;
+import org.apache.directory.scim.spec.resources.ScimResource;
+import org.apache.directory.scim.spec.resources.TypedAttribute;
+import org.apache.directory.scim.spec.schema.AttributeContainer;
+import org.apache.directory.scim.spec.schema.Schema;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class PatchGenerator {
+
+  private final SchemaRegistry schemaRegistry;
+
+  public PatchGenerator(SchemaRegistry schemaRegistry) {
+    this.schemaRegistry = schemaRegistry;
+  }
+
+  public <T extends ScimResource> List<PatchOperation> diff(T original, T modified) {
+    return new UpdateRequest<T>(original, modified, schemaRegistry).getPatchOperations();
+  }
+
+  static private class UpdateRequest<T extends ScimResource> {
+
+    private static final Logger log = LoggerFactory.getLogger(UpdateRequest.class);
+
+    private static final String OPERATION = "op";
+    private static final String PATH = "path";
+    private static final String VALUE = "value";
+
+    // Create a Jackson ObjectMapper that reads JaxB annotations
+    private final ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
+
+    private final T resource;
+    private final T original;
+
+    private final Schema schema;
+
+    private final SchemaRegistry schemaRegistry;
+
+    private final Map<Schema.Attribute, Integer> addRemoveOffsetMap = new HashMap<>();
+
+    private UpdateRequest(T original, T resource, SchemaRegistry schemaRegistry) {
+      this.schemaRegistry = schemaRegistry;
+      this.original = original;
+      this.resource = resource;
+      this.schema = schemaRegistry.getSchema(original.getBaseUrn());
+    }
+
+    public List<PatchOperation> getPatchOperations() {
+      try {
+          return createPatchOperations();
+        } catch (IllegalArgumentException | IllegalAccessException | JsonProcessingException e) {
+          throw new IllegalStateException("Error creating the patch list", e);
+        }
+    }
+
+    private void sortMultiValuedCollections(Object obj1, Object obj2, AttributeContainer ac) throws IllegalArgumentException {
+      for (Schema.Attribute attribute : ac.getAttributes()) {
+        Schema.AttributeAccessor accessor = attribute.getAccessor();
+        if (attribute.isMultiValued()) {
+          @SuppressWarnings("unchecked")
+          List<Object> collection1 = obj1 != null ? (List<Object>) accessor.get(obj1) : null;
+          @SuppressWarnings("unchecked")
+          List<Object> collection2 = obj2 != null ? (List<Object>) accessor.get(obj2) : null;
+
+          Set<Object> priorities = findCommonElements(collection1, collection2);
+          PrioritySortingComparator prioritySortingComparator = new PrioritySortingComparator(priorities);
+          if (collection1 != null) {
+            Collections.sort(collection1, prioritySortingComparator);
+          }
+
+          if (collection2 != null) {
+            Collections.sort(collection2, prioritySortingComparator);
+          }
+        } else if (attribute.getType() == Schema.Attribute.Type.COMPLEX) {
+          Object nextObj1 = obj1 != null ? accessor.get(obj1) : null;
+          Object nextObj2 = obj2 != null ? accessor.get(obj2) : null;
+          sortMultiValuedCollections(nextObj1, nextObj2, attribute);
+        }
+      }
+    }
+
+    private Set<Object> findCommonElements(List<Object> list1, List<Object> list2) {
+      if (list1 == null || list2 == null) {
+        return Collections.emptySet();
+      }
+
+      Set<Object> set1 = new HashSet<>(list1);
+      Set<Object> set2 = new HashSet<>(list2);
+
+      set1 = set1.stream().map(PrioritySortingComparator::getComparableValue).collect(Collectors.toSet());
+      set2 = set2.stream().map(PrioritySortingComparator::getComparableValue).collect(Collectors.toSet());
+
+      set1.retainAll(set2);
+      return set1;
+    }
+
+    /**
+     * There is a know issue with the diffing tool that the tool will attempt to move empty arrays. By
+     * nulling out the empty arrays during comparison, this will prevent that error from occurring. Because
+     * deleting requires the parent node
+     * @param node Parent node.
+     */
+    private static void nullEmptyLists(JsonNode node) {
+      List<String> objectsToDelete = new ArrayList<>();
+
+      if (node != null) {
+        Iterator<Map.Entry<String, JsonNode>> children = node.fields();
+        while(children.hasNext()) {
+          Map.Entry<String, JsonNode> child = children.next();
+          String name = child.getKey();
+          JsonNode childNode = child.getValue();
+
+          //Attempt to delete children before analyzing 
+          if (childNode.isContainerNode()) {
+            nullEmptyLists(childNode);
+          }
+
+          if (childNode instanceof ArrayNode) {
+            ArrayNode ar = (ArrayNode)childNode;
+            if (ar.size() == 0) {
+              objectsToDelete.add(name);
+            }
+          }
+        }
+
+        if (!objectsToDelete.isEmpty() && node instanceof ObjectNode) {
+          ObjectNode on = (ObjectNode)node;
+          for(String name : objectsToDelete) {
+            on.remove(name);
+          }
+        }
+      }
+    }
+
+    private List<PatchOperation> createPatchOperations() throws IllegalArgumentException, IllegalAccessException, JsonProcessingException {
+
+      sortMultiValuedCollections(this.original, this.resource, schema);
+      Map<String, ScimExtension> originalExtensions = this.original.getExtensions();
+      Map<String, ScimExtension> resourceExtensions = this.resource.getExtensions();
+      Set<String> keys = new HashSet<>();
+      keys.addAll(originalExtensions.keySet());
+      keys.addAll(resourceExtensions.keySet());
+
+      for(String key: keys) {
+        Schema extSchema = schemaRegistry.getSchema(key);
+        ScimExtension originalExtension = originalExtensions.get(key);
+        ScimExtension resourceExtension = resourceExtensions.get(key);
+        sortMultiValuedCollections(originalExtension, resourceExtension, extSchema);
+      }
+
+      JsonNode node1 = objectMapper.valueToTree(original);
+      nullEmptyLists(node1);
+      JsonNode node2 = objectMapper.valueToTree(resource);
+      nullEmptyLists(node2);
+      JsonNode differences = JsonDiff.asJson(node1, node2, DiffFlags.dontNormalizeOpIntoMoveAndCopy());
+
+      List<PatchOperation> patchOps = convertToPatchOperations(differences);
+      return patchOps;
+    }
+
+    List<PatchOperation> convertToPatchOperations(JsonNode node) throws IllegalArgumentException, IllegalAccessException, JsonProcessingException {
+      List<PatchOperation> operations = new ArrayList<>();
+      if (node == null) {
+        return Collections.emptyList();
+      }
+
+      if (!(node instanceof ArrayNode)) {
+        throw new RuntimeException("Expecting an instance of a ArrayNode, but got: " + node.getClass());
+      }
+
+      ArrayNode root = (ArrayNode) node;
+      for (int i = 0; i < root.size(); i++) {
+        ObjectNode patchNode = (ObjectNode) root.get(i);
+        JsonNode operationNode = patchNode.get(OPERATION);
+        JsonNode pathNode = patchNode.get(PATH);
+        JsonNode valueNode = patchNode.get(VALUE);
+
+        List<PatchOperation> nodeOperations = convertNodeToPatchOperations(operationNode.asText(), pathNode.asText(), valueNode);
+        if (!nodeOperations.isEmpty()) {
+          operations.addAll(nodeOperations);
+        }
+      }
+
+      return operations;
+    }
+
+    private List<PatchOperation> convertNodeToPatchOperations(String operationNode, String diffPath, JsonNode valueNode) throws IllegalArgumentException, IllegalAccessException, JsonProcessingException {
+      log.debug("convertNodeToPatchOperations: {} , {}", operationNode, diffPath);
+      List<PatchOperation> operations = new ArrayList<>();
+      PatchOperation.Type patchOpType = PatchOperation.Type.valueOf(operationNode.toUpperCase(Locale.ROOT));
+
+      if (diffPath != null && diffPath.length() >= 1) {
+        ParseData parseData = new ParseData(diffPath);
+
+        if (parseData.pathParts.isEmpty()) {
+          operations.add(handleExtensions(valueNode, patchOpType, parseData));
+        } else {
+          operations.addAll(handleAttributes(valueNode, patchOpType, parseData));
+        }
+      }
+
+      return operations;
+    }
+
+    private PatchOperation handleExtensions(JsonNode valueNode, PatchOperation.Type patchOpType, ParseData parseData) throws JsonProcessingException {
+      PatchOperation operation = new PatchOperation();
+      operation.setOperation(patchOpType);
+
+      AttributeReference attributeReference = new AttributeReference(parseData.pathUri, null);
+      PatchOperationPath patchOperationPath = new PatchOperationPath();
+      ValuePathExpression valuePathExpression = new ValuePathExpression(attributeReference);
+      patchOperationPath.setValuePathExpression(valuePathExpression);
+
+      operation.setPath(patchOperationPath);
+      operation.setValue(determineValue(patchOpType, valueNode, parseData));
+
+      return operation;
+    }
+
+    @SuppressWarnings("unchecked")
+    private List<PatchOperation> handleAttributes(JsonNode valueNode, PatchOperation.Type patchOpType, ParseData parseData) throws IllegalAccessException, JsonProcessingException {
+      log.trace("in handleAttributes");
+      List<PatchOperation> operations = new ArrayList<>();
+
+      List<String> attributeReferenceList = new ArrayList<>();
+      FilterExpression valueFilterExpression = null;
+      List<String> subAttributes = new ArrayList<>();
+
+      boolean processingMultiValued = false;
+      boolean processedMultiValued = false;
+      boolean done = false;
+
+      int i = 0;
+      for (String pathPart : parseData.pathParts) {
+        log.trace("path part: {}", pathPart);
+        if (done) {
+          throw new RuntimeException("Path should be done... Attribute not supported by the schema: " + pathPart);
+        } else if (processingMultiValued) {
+          parseData.traverseObjectsInArray(pathPart, patchOpType);
+
+          if (!parseData.isLastIndex(i) || patchOpType != PatchOperation.Type.ADD) {
+            if (parseData.originalObject instanceof TypedAttribute) {
+              TypedAttribute typedAttribute = (TypedAttribute) parseData.originalObject;
+              String type = typedAttribute.getType();
+              valueFilterExpression = new AttributeComparisonExpression(new AttributeReference("type"), CompareOperator.EQ, type);
+            } else if (parseData.originalObject instanceof String || parseData.originalObject instanceof Number) {
+              String toString = parseData.originalObject.toString();
+              valueFilterExpression = new AttributeComparisonExpression(new AttributeReference("value"), CompareOperator.EQ, toString);
+            } else if(parseData.originalObject instanceof Enum) {
+              Enum<?> tempEnum = (Enum<?>)parseData.originalObject;
+              valueFilterExpression = new AttributeComparisonExpression(new AttributeReference("value"), CompareOperator.EQ, tempEnum.name());
+            } else {
+              if (parseData.originalObject != null) {
+                log.debug("Attribute: {} doesn't implement TypedAttribute, can't create ValueFilterExpression", parseData.originalObject.getClass());
+              } else {
+                log.debug("Attribute: null doesn't implement TypedAttribute, can't create ValueFilterExpression");
+              }
+              valueFilterExpression = new AttributeComparisonExpression(new AttributeReference("value"), CompareOperator.EQ, "?");
+            }
+            processingMultiValued = false;
+            processedMultiValued = true;
+          }
+        } else {
+          Schema.Attribute attribute = parseData.ac.getAttribute(pathPart);
+
+          if (attribute != null) {
+            if (processedMultiValued) {
+              subAttributes.add(pathPart);
+            } else {
+              log.debug("Adding {} to attributeReferenceList", pathPart);
+              attributeReferenceList.add(pathPart);
+            }
+
+            parseData.traverseObjects(pathPart, attribute);
+
+            if (patchOpType == PatchOperation.Type.REPLACE &&
+              (parseData.resourceObject != null && parseData.resourceObject instanceof Collection && !((Collection<?>)parseData.resourceObject).isEmpty()) &&
+              (parseData.originalObject == null ||
+                (parseData.originalObject instanceof Collection && ((Collection<?>)parseData.originalObject).isEmpty()))) {
+              patchOpType = PatchOperation.Type.ADD;
+            }
+
+            if (attribute.isMultiValued()) {
+              processingMultiValued = true;
+            } else if (attribute.getType() != Schema.Attribute.Type.COMPLEX) {
+              done = true;
+            }
+          }
+        }
+        ++i;
+      }
+
+      if (patchOpType == PatchOperation.Type.REPLACE && (parseData.resourceObject == null ||
+        (parseData.resourceObject instanceof Collection && ((Collection<?>)parseData.resourceObject).isEmpty()))) {
+        patchOpType = PatchOperation.Type.REMOVE;
+        valueNode = null;
+      }
+
+      if (patchOpType == PatchOperation.Type.REPLACE && parseData.originalObject == null) {
+        patchOpType = PatchOperation.Type.ADD;
+      }
+
+      if (!attributeReferenceList.isEmpty()) {
+        Object value = determineValue(patchOpType, valueNode, parseData);
+
+        if (value != null && value instanceof ArrayList) {
+          List<Object> objList = (List<Object>)value;
+
+          if (!objList.isEmpty()) {
+            Object firstElement = objList.get(0);
+            if (firstElement instanceof ArrayList) {
+              objList = (List<Object>) firstElement;
+            }
+
+            for (Object obj : objList) {
+              PatchOperation operation = buildPatchOperation(patchOpType, parseData, attributeReferenceList, valueFilterExpression, subAttributes, obj);
+              if (operation != null) {
+                operations.add(operation);
+              }
+            }
+          }
+        } else {
+          PatchOperation operation = buildPatchOperation(patchOpType, parseData, attributeReferenceList, valueFilterExpression, subAttributes, value);
+          if (operation != null) {
+            operations.add(operation);
+          }
+        }
+      }
+
+      return operations;
+    }
+
+    private PatchOperation buildPatchOperation(PatchOperation.Type patchOpType, ParseData parseData, List<String> attributeReferenceList,
+                                               FilterExpression valueFilterExpression, List<String> subAttributes, Object value) {
+      PatchOperation operation = new PatchOperation();
+      operation.setOperation(patchOpType);
+      String attribute = attributeReferenceList.get(0);
+      String subAttribute = attributeReferenceList.size() > 1 ? attributeReferenceList.get(1) : null;
+
+      if (subAttribute == null && !subAttributes.isEmpty()) {
+        subAttribute = subAttributes.get(0);
+      }
+      AttributeReference attributeReference = new AttributeReference(parseData.pathUri, attribute, subAttribute);
+      PatchOperationPath patchOperationPath = new PatchOperationPath();
+      ValuePathExpression valuePathExpression = new ValuePathExpression(attributeReference, valueFilterExpression);
+      patchOperationPath.setValuePathExpression(valuePathExpression);
+
+      operation.setPath(patchOperationPath);
+      operation.setValue(value);
+
+      return operation;
+    }
+
+    private Object determineValue(PatchOperation.Type patchOpType, JsonNode valueNode, ParseData parseData) throws JsonProcessingException {
+      if (patchOpType == PatchOperation.Type.REMOVE) {
+        return null;
+      }
+
+      if (valueNode != null) {
+        if (valueNode instanceof TextNode) {
+          return valueNode.asText();
+        } else if (valueNode instanceof BooleanNode) {
+          return valueNode.asBoolean();
+        } else if (valueNode instanceof DoubleNode || valueNode instanceof FloatNode) {
+          return valueNode.asDouble();
+        } else if (valueNode instanceof IntNode) {
+          return valueNode.asInt();
+        } else if (valueNode instanceof NullNode) {
+          return null;
+        } else if (valueNode instanceof ObjectNode) {
+          return parseData.resourceObject;
+        } else if (valueNode instanceof POJONode) {
+          POJONode pojoNode = (POJONode) valueNode;
+          return pojoNode.getPojo();
+        } else if (valueNode instanceof ArrayNode) {
+          ArrayNode arrayNode = (ArrayNode) valueNode;
+          List<Object> objectList = new ArrayList<>();
+          for(int i = 0; i < arrayNode.size(); i++) {
+            Object subObject = determineValue(patchOpType, arrayNode.get(i), parseData);
+            if (subObject != null) {
+              objectList.add(subObject);
+            }
+          }
+          return objectList;
+        }
+      }
+      return null;
+    }
+
+    private class ParseData {
+
+      List<String> pathParts;
+      Object originalObject;
+      Object resourceObject;
+      AttributeContainer ac;
+      String pathUri;
+
+      public ParseData(String diffPath) {
+        String path = diffPath.substring(1);
+        pathParts = new ArrayList<>(Arrays.asList(path.split("/")));
+
+        // Extract namespace
+        pathUri = null;
+
+        String firstPathPart = pathParts.get(0);
+        if (firstPathPart.contains(":")) {
+          pathUri = firstPathPart;
+          pathParts.remove(0);
+        }
+
+        if (pathUri != null) {
+          ac = schemaRegistry.getSchema(pathUri);
+          originalObject = original.getExtension(pathUri);
+          resourceObject = resource.getExtension(pathUri);
+        } else {
+          ac = schema;
+          originalObject = original;
+          resourceObject = resource;
+        }
+      }
+
+      public void traverseObjects(String pathPart, Schema.Attribute attribute) throws IllegalArgumentException, IllegalAccessException {
+        originalObject = lookupAttribute(originalObject, ac, pathPart);
+        resourceObject = lookupAttribute(resourceObject, ac, pathPart);
+        ac = attribute;
+      }
+
+      public void traverseObjectsInArray(String pathPart, PatchOperation.Type patchOpType) {
+        int index = Integer.parseInt(pathPart);
+
+        Schema.Attribute attr = (Schema.Attribute) ac;
+
+        Integer addRemoveOffset = addRemoveOffsetMap.getOrDefault(attr, 0);
+        switch (patchOpType) {
+          case ADD:
+            addRemoveOffsetMap.put(attr, addRemoveOffset - 1);
+            break;
+          case REMOVE:
+            addRemoveOffsetMap.put(attr, addRemoveOffset + 1);
+            break;
+          case REPLACE:
+          default:
+            // Do Nothing
+            break;
+        }
+
+        int newindex = index + addRemoveOffset;
+        if (newindex < 0) {
+          log.error("Attempting to retrieve a negative index:{} on pathPath: {}", newindex, pathPart);
+        }
+
+        originalObject = lookupIndexInArray(originalObject, newindex);
+        resourceObject = lookupIndexInArray(resourceObject, index);
+      }
+
+      public boolean isLastIndex(int index) {
+        int numPathParts = pathParts.size();
+        return index == (numPathParts - 1);
+      }
+
+      @SuppressWarnings("rawtypes")
+      private Object lookupIndexInArray(Object object, int index) {
+        if (!(object instanceof List)) {
+          throw new RuntimeException("Unsupported collection type: " + object.getClass());
+        }
+        List list = (List) object;
+        if (index >= list.size()) {
+          return null;
+        }
+
+        return list.get(index);
+      }
+
+      private Object lookupAttribute(Object object, AttributeContainer ac, String attributeName) throws IllegalArgumentException, IllegalAccessException {
+        if (object == null) {
+          return null;
+        }
+
+        Schema.Attribute attribute = ac.getAttribute(attributeName);
+        return attribute.getAccessor().get(object);
+      }
+    }
+  }
+}
diff --git a/scim-core/src/main/java/org/apache/directory/scim/core/repository/Repository.java b/scim-core/src/main/java/org/apache/directory/scim/core/repository/Repository.java
index 1e59830a..a84121e3 100644
--- a/scim-core/src/main/java/org/apache/directory/scim/core/repository/Repository.java
+++ b/scim-core/src/main/java/org/apache/directory/scim/core/repository/Repository.java
@@ -21,12 +21,15 @@ package org.apache.directory.scim.core.repository;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 import org.apache.directory.scim.spec.exception.ResourceException;
 import org.apache.directory.scim.spec.filter.FilterResponse;
 import org.apache.directory.scim.spec.filter.Filter;
 import org.apache.directory.scim.spec.filter.PageRequest;
 import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
 import org.apache.directory.scim.spec.resources.ScimExtension;
 import org.apache.directory.scim.spec.resources.ScimResource;
 
@@ -60,14 +63,31 @@ public interface Repository<T extends ScimResource> {
   /**
    * Allows the SCIM server's REST implementation to update and existing
    * resource via a PUT to a valid end-point.
-   * 
-   * @param updateRequest The ScimResource to update and persist.
+   *
+   * @param id the identifier of the ScimResource to update and persist.
+   * @param version an optional version (usually used as an ETag) that be used to optimize update requests, may be compared against, the current {@code ScimResource.meta.version}.
+   * @param resource an updated resource to persist
+   * @param includedAttributes optional set of attributes to include from ScimResource, may be used to optimize queries.
+   * @param excludedAttributes optional set of attributes to exclude from ScimResource, may be used to optimize queries.
    * @return The newly updated ScimResource.
-   * @throws ResourceException When the ScimResource cannot be
-   *         updated.
+   * @throws ResourceException When the ScimResource cannot be updated.
    */
-  T update(UpdateRequest<T> updateRequest) throws ResourceException;
-  
+  T update(String id, String version, T resource, Set<AttributeReference> includedAttributes, Set<AttributeReference> excludedAttributes) throws ResourceException;
+
+  /**
+   * Allows the SCIM server's REST implementation to update and existing
+   * resource via a PATCH to a valid end-point.
+   *
+   * @param id the identifier of the ScimResource to update and persist.
+   * @param version an optional version (usually used as an ETag) that be used to optimize update requests, may be compared against, the current {@code ScimResource.meta.version}.
+   * @param patchOperations a list of patch operations to apply to an existing resource.
+   * @param includedAttributes optional set of attributes to include from ScimResource, may be used to optimize queries.
+   * @param excludedAttributes optional set of attributes to exclude from ScimResource, may be used to optimize queries.
+   * @return The newly updated ScimResource.
+   * @throws ResourceException When the ScimResource cannot be updated.
+   */
+  T patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributes, Set<AttributeReference> excludedAttributes) throws ResourceException;
+
   /**
    * Retrieves the ScimResource associated with the provided identifier.
    * @param id The identifier of the target ScimResource.
diff --git a/scim-core/src/main/java/org/apache/directory/scim/core/repository/UpdateRequest.java b/scim-core/src/main/java/org/apache/directory/scim/core/repository/UpdateRequest.java
deleted file mode 100644
index a39adac3..00000000
--- a/scim-core/src/main/java/org/apache/directory/scim/core/repository/UpdateRequest.java
+++ /dev/null
@@ -1,589 +0,0 @@
-/*
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements.  See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership.  The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License.  You may obtain a copy of the License at
- 
-* http://www.apache.org/licenses/LICENSE-2.0
-
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied.  See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-
-package org.apache.directory.scim.core.repository;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import com.fasterxml.jackson.databind.node.BooleanNode;
-import com.fasterxml.jackson.databind.node.DoubleNode;
-import com.fasterxml.jackson.databind.node.FloatNode;
-import com.fasterxml.jackson.databind.node.IntNode;
-import com.fasterxml.jackson.databind.node.NullNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import com.fasterxml.jackson.databind.node.POJONode;
-import com.fasterxml.jackson.databind.node.TextNode;
-import com.flipkart.zjsonpatch.DiffFlags;
-import com.flipkart.zjsonpatch.JsonDiff;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-import lombok.ToString;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.directory.scim.core.json.ObjectMapperFactory;
-import org.apache.directory.scim.core.schema.SchemaRegistry;
-import org.apache.directory.scim.spec.filter.*;
-import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
-import org.apache.directory.scim.spec.patch.PatchOperation;
-import org.apache.directory.scim.spec.patch.PatchOperation.Type;
-import org.apache.directory.scim.spec.patch.PatchOperationPath;
-import org.apache.directory.scim.spec.resources.ScimExtension;
-import org.apache.directory.scim.spec.resources.ScimResource;
-import org.apache.directory.scim.spec.resources.TypedAttribute;
-import org.apache.directory.scim.spec.schema.AttributeContainer;
-import org.apache.directory.scim.spec.schema.Schema;
-import org.apache.directory.scim.spec.schema.Schema.Attribute;
-
-import java.util.*;
-import java.util.stream.Collectors;
-
-@Slf4j
-@EqualsAndHashCode
-@ToString
-public class UpdateRequest<T extends ScimResource> {
-  
-  private static final String OPERATION = "op";
-  private static final String PATH = "path";
-  private static final String VALUE = "value";
-
-  // Create a Jackson ObjectMapper that reads JaxB annotations
-  private final ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
-
-  @Getter
-  private String id;
-  private T resource;
-  @Getter
-  private T original;
-  private List<PatchOperation> patchOperations;
-  private boolean initialized = false;
-
-  private Schema schema;
-
-  private PatchHandler patchHandler;
-
-  private SchemaRegistry schemaRegistry;
-
-  private Map<Attribute, Integer> addRemoveOffsetMap = new HashMap<>();
-
-  public UpdateRequest(SchemaRegistry schemaRegistry) {
-    this.schemaRegistry = schemaRegistry;
-  }
-
-  public UpdateRequest(String id, T original, T resource, SchemaRegistry schemaRegistry) {
-    this(schemaRegistry);
-    this.id = id;
-    this.original = original;
-    this.resource = resource;
-    this.patchHandler = new PatchHandlerImpl(schemaRegistry);
-    this.schema = schemaRegistry.getSchema(original.getBaseUrn());
-    initialized = true;
-  }
-
-  public UpdateRequest(String id, T original, List<PatchOperation> patchOperations, SchemaRegistry schemaRegistry) {
-    this(schemaRegistry);
-    this.id = id;
-    this.original = original;
-    this.patchOperations = patchOperations;
-    this.patchHandler = new PatchHandlerImpl(schemaRegistry);
-    this.schema = schemaRegistry.getSchema(original.getBaseUrn());
-
-    initialized = true;
-  }
-
-  public T getResource() {
-    if (!initialized) {
-      throw new IllegalStateException("UpdateRequest was not initialized");
-    }
-
-    if (resource == null) {
-      this.resource = this.patchHandler.apply(this.original, this.patchOperations);
-    }
-
-    return this.resource;
-  }
-
-  public List<PatchOperation> getPatchOperations() {
-    if (!initialized) {
-      throw new IllegalStateException("UpdateRequest was not initialized");
-    }
-    
-    if (patchOperations == null) {
-      try {
-        patchOperations = createPatchOperations(); 
-      } catch (IllegalArgumentException | IllegalAccessException | JsonProcessingException e) {
-        throw new IllegalStateException("Error creating the patch list", e);
-      }
-    }
-    
-    return patchOperations;
-  }
-
-  private void sortMultiValuedCollections(Object obj1, Object obj2, AttributeContainer ac) throws IllegalArgumentException {
-    for (Attribute attribute : ac.getAttributes()) {
-      Schema.AttributeAccessor accessor = attribute.getAccessor();
-      if (attribute.isMultiValued()) {
-        @SuppressWarnings("unchecked")
-        List<Object> collection1 = obj1 != null ? (List<Object>) accessor.get(obj1) : null;
-        @SuppressWarnings("unchecked")
-        List<Object> collection2 = obj2 != null ? (List<Object>) accessor.get(obj2) : null;
-        
-        Set<Object> priorities = findCommonElements(collection1, collection2);
-        PrioritySortingComparator prioritySortingComparator = new PrioritySortingComparator(priorities);
-        if (collection1 != null) {
-          Collections.sort(collection1, prioritySortingComparator);
-        }
-        
-        if (collection2 != null) {
-          Collections.sort(collection2, prioritySortingComparator);
-        }
-      } else if (attribute.getType() == Attribute.Type.COMPLEX) {
-        Object nextObj1 = obj1 != null ? accessor.get(obj1) : null;
-        Object nextObj2 = obj2 != null ? accessor.get(obj2) : null;
-        sortMultiValuedCollections(nextObj1, nextObj2, attribute);
-      }
-    }
-  }
-
-  private Set<Object> findCommonElements(List<Object> list1, List<Object> list2) {
-    if (list1 == null || list2 == null) {
-      return Collections.emptySet();
-    }
-    
-    Set<Object> set1 = new HashSet<>(list1);
-    Set<Object> set2 = new HashSet<>(list2);
-    
-    set1 = set1.stream().map(PrioritySortingComparator::getComparableValue).collect(Collectors.toSet());
-    set2 = set2.stream().map(PrioritySortingComparator::getComparableValue).collect(Collectors.toSet());
-    
-    set1.retainAll(set2);
-    return set1;
-  }
-
-  /**
-   * There is a know issue with the diffing tool that the tool will attempt to move empty arrays. By
-   * nulling out the empty arrays during comparison, this will prevent that error from occurring. Because
-   * deleting requires the parent node
-   * @param node Parent node.
-   */
-  private static void nullEmptyLists(JsonNode node) {
-    List<String> objectsToDelete = new ArrayList<>();
-    
-    if (node != null) {
-      Iterator<Map.Entry<String, JsonNode>> children = node.fields();
-      while(children.hasNext()) {
-        Map.Entry<String, JsonNode> child = children.next();
-        String name = child.getKey();
-        JsonNode childNode = child.getValue();
-        
-        //Attempt to delete children before analyzing 
-        if (childNode.isContainerNode()) {
-          nullEmptyLists(childNode);
-        }
-        
-        if (childNode instanceof ArrayNode) {
-          ArrayNode ar = (ArrayNode)childNode;
-          if (ar.size() == 0) {
-            objectsToDelete.add(name);
-          }
-        }
-      }
-      
-      if (!objectsToDelete.isEmpty() && node instanceof ObjectNode) {
-        ObjectNode on = (ObjectNode)node;
-        for(String name : objectsToDelete) {
-          on.remove(name);
-        }
-      }
-    }
-  }
-
-  private List<PatchOperation> createPatchOperations() throws IllegalArgumentException, IllegalAccessException, JsonProcessingException {
-
-    sortMultiValuedCollections(this.original, this.resource, schema);
-    Map<String, ScimExtension> originalExtensions = this.original.getExtensions();
-    Map<String, ScimExtension> resourceExtensions = this.resource.getExtensions();
-    Set<String> keys = new HashSet<>();
-    keys.addAll(originalExtensions.keySet());
-    keys.addAll(resourceExtensions.keySet());
-    
-    for(String key: keys) {
-      Schema extSchema = schemaRegistry.getSchema(key);
-      ScimExtension originalExtension = originalExtensions.get(key);
-      ScimExtension resourceExtension = resourceExtensions.get(key);
-      sortMultiValuedCollections(originalExtension, resourceExtension, extSchema);
-    }
-
-    JsonNode node1 = objectMapper.valueToTree(original);
-    nullEmptyLists(node1);
-    JsonNode node2 = objectMapper.valueToTree(resource);
-    nullEmptyLists(node2);
-    JsonNode differences = JsonDiff.asJson(node1, node2, DiffFlags.dontNormalizeOpIntoMoveAndCopy());
-    
-    
-    /*
-    Commenting out debug statement to prevent PII from appearing in log
-    ObjectWriter writer = objMapper.writerWithDefaultPrettyPrinter();
-    try {
-      log.debug("Original: "+writer.writeValueAsString(node1));
-      log.debug("Resource: "+writer.writeValueAsString(node2));
-    } catch (IOException e) {
-      
-    }*/
-
-    /*try {
-      log.debug("Differences: " + objMapper.writerWithDefaultPrettyPrinter().writeValueAsString(differences));
-    } catch (JsonProcessingException e) {
-      log.debug("Unable to debug differences: ", e);
-    }*/
-
-    List<PatchOperation> patchOps = convertToPatchOperations(differences);
-
-    /*try {
-      log.debug("Patch Ops: " + objMapper.writerWithDefaultPrettyPrinter().writeValueAsString(patchOps));
-    } catch (JsonProcessingException e) {
-      log.debug("Unable to debug patch ops: ", e);
-    }*/
-
-    return patchOps;
-  }
-
-  List<PatchOperation> convertToPatchOperations(JsonNode node) throws IllegalArgumentException, IllegalAccessException, JsonProcessingException {
-    List<PatchOperation> operations = new ArrayList<>();
-    if (node == null) {
-      return Collections.emptyList();
-    }
-
-    if (!(node instanceof ArrayNode)) {
-      throw new RuntimeException("Expecting an instance of a ArrayNode, but got: " + node.getClass());
-    }
-
-    ArrayNode root = (ArrayNode) node;
-    for (int i = 0; i < root.size(); i++) {
-      ObjectNode patchNode = (ObjectNode) root.get(i);
-      JsonNode operationNode = patchNode.get(OPERATION);
-      JsonNode pathNode = patchNode.get(PATH);
-      JsonNode valueNode = patchNode.get(VALUE);
-
-      List<PatchOperation> nodeOperations = convertNodeToPatchOperations(operationNode.asText(), pathNode.asText(), valueNode);
-      if (!nodeOperations.isEmpty()) {
-        operations.addAll(nodeOperations);
-      }
-    }    
-    
-    return operations;
-  }
-
-  private List<PatchOperation> convertNodeToPatchOperations(String operationNode, String diffPath, JsonNode valueNode) throws IllegalArgumentException, IllegalAccessException, JsonProcessingException {
-    log.debug("convertNodeToPatchOperations: {} , {}", operationNode, diffPath);
-    List<PatchOperation> operations = new ArrayList<>();
-    PatchOperation.Type patchOpType = PatchOperation.Type.valueOf(operationNode.toUpperCase(Locale.ROOT));
-
-    if (diffPath != null && diffPath.length() >= 1) {
-      ParseData parseData = new ParseData(diffPath);
-      
-      if (parseData.pathParts.isEmpty()) {
-        operations.add(handleExtensions(valueNode, patchOpType, parseData));
-      } else {
-        operations.addAll(handleAttributes(valueNode, patchOpType, parseData));
-      }
-    }
-    
-    return operations;
-  }
-
-  private PatchOperation handleExtensions(JsonNode valueNode, Type patchOpType, ParseData parseData) throws JsonProcessingException {
-    PatchOperation operation = new PatchOperation();
-    operation.setOperation(patchOpType);
-    
-    AttributeReference attributeReference = new AttributeReference(parseData.pathUri, null);
-    PatchOperationPath patchOperationPath = new PatchOperationPath();
-    ValuePathExpression valuePathExpression = new ValuePathExpression(attributeReference);
-    patchOperationPath.setValuePathExpression(valuePathExpression);
-    
-    operation.setPath(patchOperationPath);
-    operation.setValue(determineValue(patchOpType, valueNode, parseData));
-    
-    return operation;
-  }
-
-  @SuppressWarnings("unchecked")
-  private List<PatchOperation> handleAttributes(JsonNode valueNode, PatchOperation.Type patchOpType, ParseData parseData) throws IllegalAccessException, JsonProcessingException {
-    log.trace("in handleAttributes");
-    List<PatchOperation> operations = new ArrayList<>();
-    
-    List<String> attributeReferenceList = new ArrayList<>();
-    FilterExpression valueFilterExpression = null;
-    List<String> subAttributes = new ArrayList<>();
-
-    boolean processingMultiValued = false;
-    boolean processedMultiValued = false;
-    boolean done = false;
-    
-    int i = 0;
-    for (String pathPart : parseData.pathParts) {
-      log.trace("path part: {}", pathPart);
-      if (done) {
-        throw new RuntimeException("Path should be done... Attribute not supported by the schema: " + pathPart);
-      } else if (processingMultiValued) {
-        parseData.traverseObjectsInArray(pathPart, patchOpType);
-
-        if (!parseData.isLastIndex(i) || patchOpType != PatchOperation.Type.ADD) {
-          if (parseData.originalObject instanceof TypedAttribute) {
-            TypedAttribute typedAttribute = (TypedAttribute) parseData.originalObject;
-            String type = typedAttribute.getType();
-            valueFilterExpression = new AttributeComparisonExpression(new AttributeReference("type"), CompareOperator.EQ, type);
-          } else if (parseData.originalObject instanceof String || parseData.originalObject instanceof Number) {
-            String toString = parseData.originalObject.toString();
-            valueFilterExpression = new AttributeComparisonExpression(new AttributeReference("value"), CompareOperator.EQ, toString);
-          } else if(parseData.originalObject instanceof Enum) {
-            Enum<?> tempEnum = (Enum<?>)parseData.originalObject;
-            valueFilterExpression = new AttributeComparisonExpression(new AttributeReference("value"), CompareOperator.EQ, tempEnum.name());
-          } else {
-            if (parseData.originalObject != null) {
-              log.debug("Attribute: {} doesn't implement TypedAttribute, can't create ValueFilterExpression", parseData.originalObject.getClass());
-            } else {
-              log.debug("Attribute: null doesn't implement TypedAttribute, can't create ValueFilterExpression");
-            }
-            valueFilterExpression = new AttributeComparisonExpression(new AttributeReference("value"), CompareOperator.EQ, "?");
-          }
-          processingMultiValued = false;
-          processedMultiValued = true;
-        }
-      } else {
-        Attribute attribute = parseData.ac.getAttribute(pathPart);
-        
-        if (attribute != null) {
-          if (processedMultiValued) {
-            subAttributes.add(pathPart);
-          } else {
-            log.debug("Adding {} to attributeReferenceList", pathPart);
-            attributeReferenceList.add(pathPart);
-          }
-  
-          parseData.traverseObjects(pathPart, attribute);
-  
-          if (patchOpType == Type.REPLACE && 
-              (parseData.resourceObject != null && parseData.resourceObject instanceof Collection && !((Collection<?>)parseData.resourceObject).isEmpty()) &&
-              (parseData.originalObject == null || 
-              (parseData.originalObject instanceof Collection && ((Collection<?>)parseData.originalObject).isEmpty()))) {
-            patchOpType = Type.ADD;
-          }
-          
-          if (attribute.isMultiValued()) {
-            processingMultiValued = true;
-          } else if (attribute.getType() != Attribute.Type.COMPLEX) {
-            done = true;
-          }
-        }
-      }
-      ++i;
-    }
-    
-    if (patchOpType == Type.REPLACE && (parseData.resourceObject == null || 
-        (parseData.resourceObject instanceof Collection && ((Collection<?>)parseData.resourceObject).isEmpty()))) {
-      patchOpType = Type.REMOVE;
-      valueNode = null;
-    }
-    
-    if (patchOpType == Type.REPLACE && parseData.originalObject == null) {
-      patchOpType = Type.ADD;
-    }
-        
-    if (!attributeReferenceList.isEmpty()) {
-      Object value = determineValue(patchOpType, valueNode, parseData);
-      
-      if (value != null && value instanceof ArrayList) {
-        List<Object> objList = (List<Object>)value;
-        
-        if (!objList.isEmpty()) {
-          Object firstElement = objList.get(0); 
-          if (firstElement instanceof ArrayList) {
-            objList = (List<Object>) firstElement;
-          }
-          
-          for (Object obj : objList) {
-            PatchOperation operation = buildPatchOperation(patchOpType, parseData, attributeReferenceList, valueFilterExpression, subAttributes, obj);
-            if (operation != null) {
-              operations.add(operation);
-            }
-          }
-        }
-      } else {
-        PatchOperation operation = buildPatchOperation(patchOpType, parseData, attributeReferenceList, valueFilterExpression, subAttributes, value);
-        if (operation != null) {
-          operations.add(operation);
-        }
-      }
-    }
-    
-    return operations;
-  }
-  
-  private PatchOperation buildPatchOperation(PatchOperation.Type patchOpType, ParseData parseData, List<String> attributeReferenceList,
-                                             FilterExpression valueFilterExpression, List<String> subAttributes, Object value) {
-    PatchOperation operation = new PatchOperation();
-    operation.setOperation(patchOpType);
-    String attribute = attributeReferenceList.get(0);
-    String subAttribute = attributeReferenceList.size() > 1 ? attributeReferenceList.get(1) : null;
-
-    if (subAttribute == null && !subAttributes.isEmpty()) {
-      subAttribute = subAttributes.get(0);
-    }
-    AttributeReference attributeReference = new AttributeReference(parseData.pathUri, attribute, subAttribute);
-    PatchOperationPath patchOperationPath = new PatchOperationPath();
-    ValuePathExpression valuePathExpression = new ValuePathExpression(attributeReference, valueFilterExpression);
-    patchOperationPath.setValuePathExpression(valuePathExpression);
-
-    operation.setPath(patchOperationPath);
-    operation.setValue(value);
-    
-    return operation;
-  }
-    
-  private Object determineValue(PatchOperation.Type patchOpType, JsonNode valueNode, ParseData parseData) throws JsonProcessingException {
-    if (patchOpType == PatchOperation.Type.REMOVE) {
-      return null;
-    }
-
-    if (valueNode != null) {
-      if (valueNode instanceof TextNode) {
-        return valueNode.asText();
-      } else if (valueNode instanceof BooleanNode) {
-        return valueNode.asBoolean();
-      } else if (valueNode instanceof DoubleNode || valueNode instanceof FloatNode) {
-        return valueNode.asDouble();
-      } else if (valueNode instanceof IntNode) {
-        return valueNode.asInt();
-      } else if (valueNode instanceof NullNode) {
-        return null;
-      } else if (valueNode instanceof ObjectNode) {
-        return parseData.resourceObject;
-      } else if (valueNode instanceof POJONode) {
-        POJONode pojoNode = (POJONode) valueNode;
-        return pojoNode.getPojo();
-      } else if (valueNode instanceof ArrayNode) {
-        ArrayNode arrayNode = (ArrayNode) valueNode;
-        List<Object> objectList = new ArrayList<>();
-        for(int i = 0; i < arrayNode.size(); i++) {
-          Object subObject = determineValue(patchOpType, arrayNode.get(i), parseData);
-          if (subObject != null) {
-            objectList.add(subObject);
-          }
-        }
-        return objectList;
-      }
-    }
-    return null;
-  }
-
-  private class ParseData {
-
-    List<String> pathParts;
-    Object originalObject;
-    Object resourceObject;
-    AttributeContainer ac;
-    String pathUri;
-
-    public ParseData(String diffPath) {
-      String path = diffPath.substring(1);
-      pathParts = new ArrayList<>(Arrays.asList(path.split("/")));
-
-      // Extract namespace
-      pathUri = null;
-
-      String firstPathPart = pathParts.get(0);
-      if (firstPathPart.contains(":")) {
-        pathUri = firstPathPart;
-        pathParts.remove(0);
-      }
-
-      if (pathUri != null) {
-        ac = schemaRegistry.getSchema(pathUri);
-        originalObject = original.getExtension(pathUri);
-        resourceObject = resource.getExtension(pathUri);
-      } else {
-        ac = schema;
-        originalObject = original;
-        resourceObject = resource;
-      }
-    }
-
-    public void traverseObjects(String pathPart, Attribute attribute) throws IllegalArgumentException, IllegalAccessException {
-      originalObject = lookupAttribute(originalObject, ac, pathPart);
-      resourceObject = lookupAttribute(resourceObject, ac, pathPart);
-      ac = attribute;
-    }
-
-    public void traverseObjectsInArray(String pathPart, Type patchOpType) {
-      int index = Integer.parseInt(pathPart);
-
-      Attribute attr = (Attribute) ac;
-      
-      Integer addRemoveOffset = addRemoveOffsetMap.getOrDefault(attr, 0);
-      switch (patchOpType) {
-      case ADD:
-        addRemoveOffsetMap.put(attr, addRemoveOffset - 1);
-        break;
-      case REMOVE:
-        addRemoveOffsetMap.put(attr, addRemoveOffset + 1);
-        break;
-      case REPLACE:
-      default:
-        // Do Nothing
-        break;
-      }
-      
-      int newindex = index + addRemoveOffset;
-      if (newindex < 0) {
-        log.error("Attempting to retrieve a negative index:{} on pathPath: {}", newindex, pathPart);
-      }
-      
-      originalObject = lookupIndexInArray(originalObject, newindex);
-      resourceObject = lookupIndexInArray(resourceObject, index);
-    }
-
-    public boolean isLastIndex(int index) {
-      int numPathParts = pathParts.size();
-      return index == (numPathParts - 1);
-    }
-
-    @SuppressWarnings("rawtypes")
-    private Object lookupIndexInArray(Object object, int index) {
-      if (!(object instanceof List)) {
-        throw new RuntimeException("Unsupported collection type: " + object.getClass());
-      }
-      List list = (List) object;
-      if (index >= list.size()) {
-        return null;
-      }
-
-      return list.get(index);
-    }
-
-    private Object lookupAttribute(Object object, AttributeContainer ac, String attributeName) throws IllegalArgumentException, IllegalAccessException {
-      if (object == null) {
-        return null;
-      }
-
-      Attribute attribute = ac.getAttribute(attributeName);
-      return attribute.getAccessor().get(object);
-    }
-  }
-}
diff --git a/scim-core/src/test/java/org/apache/directory/scim/core/repository/UpdateRequestTest.java b/scim-core/src/test/java/org/apache/directory/scim/core/repository/PatchGeneratorTest.java
similarity index 84%
rename from scim-core/src/test/java/org/apache/directory/scim/core/repository/UpdateRequestTest.java
rename to scim-core/src/test/java/org/apache/directory/scim/core/repository/PatchGeneratorTest.java
index 4bf487d9..bbdcd78a 100644
--- a/scim-core/src/test/java/org/apache/directory/scim/core/repository/UpdateRequestTest.java
+++ b/scim-core/src/test/java/org/apache/directory/scim/core/repository/PatchGeneratorTest.java
@@ -19,7 +19,7 @@
 
 package org.apache.directory.scim.core.repository;
 
-import static org.apache.directory.scim.core.repository.UpdateRequestTest.PatchOperationCondition.op;
+import static org.apache.directory.scim.core.repository.PatchGeneratorTest.PatchOperationCondition.op;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.util.ArrayList;
@@ -64,7 +64,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 @Slf4j
-public class UpdateRequestTest {
+public class PatchGeneratorTest {
   
   private static final String FIRST = "first";
   private static final String SECOND = "second";
@@ -85,31 +85,13 @@ public class UpdateRequestTest {
     when(schemaRegistry.getSchema(ExampleObjectExtension.URN)).thenReturn(Schemas.schemaForExtension(ExampleObjectExtension.class));
   }
 
-  @Test
-  public void testResourcePassthrough() throws Exception {
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", createUser(), createUser(), schemaRegistry);
-    ScimUser result = updateRequest.getResource();
-    log.debug("testResourcePassthrough: {}", result);
-    assertThat(result)
-              .isNotNull();
-  }
-
-  @Test
-  public void testPatchPassthrough() throws Exception {
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", createUser(), createUser1PatchOps(), schemaRegistry);
-    List<PatchOperation> result = updateRequest.getPatchOperations();
-    log.debug("testPatchPassthrough: {}", result);
-    assertThat(result)
-              .isNotNull();
-  }
 
   @Test
   public void testAddSingleAttribute() throws Exception {
     ScimUser user1 = createUser();
     ScimUser user2 = createUser();
     user2.setNickName("Jon");
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> result = updateRequest.getPatchOperations();
+    List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     PatchOperation actual = assertSingleResult(result);
 
@@ -123,8 +105,7 @@ public class UpdateRequestTest {
     ScimUser user2 = createUser();
     user2.addExtension(ext);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> result = updateRequest.getPatchOperations();
+    List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     PatchOperation actual = assertSingleResult(result);
 
@@ -138,8 +119,7 @@ public class UpdateRequestTest {
     user2.getName()
          .setHonorificPrefix("Dr.");
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> result = updateRequest.getPatchOperations();
+    List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     PatchOperation actual = assertSingleResult(result);
 
@@ -155,8 +135,7 @@ public class UpdateRequestTest {
     mobilePhone.setPrimary(false);
     user2.getPhoneNumbers().add(mobilePhone);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> result = updateRequest.getPatchOperations();
+    List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     PatchOperation actual = assertSingleResult(result);
 
@@ -181,8 +160,8 @@ public class UpdateRequestTest {
     mobilePhone.setPrimary(true);
     user2.getPhoneNumbers().add(mobilePhone);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> operations = updateRequest.getPatchOperations();
+    List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
+
     assertNotNull(operations);
     assertThat(operations).hasSize(1);
     PatchOperation operation = operations.get(0);
@@ -210,8 +189,7 @@ public class UpdateRequestTest {
     user2.getPhoneNumbers().add(homePhone);
 
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> operations = updateRequest.getPatchOperations();
+    List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
     assertNotNull(operations);
     assertEquals(2, operations.size());
     
@@ -232,8 +210,7 @@ public class UpdateRequestTest {
     ScimUser user2 = createUser();
     user2.setActive(false);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> result = updateRequest.getPatchOperations();
+    List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     PatchOperation actual = assertSingleResult(result);
 
@@ -246,8 +223,7 @@ public class UpdateRequestTest {
     ScimUser user2 = createUser();
     user2.getExtension(EnterpriseExtension.class).setDepartment("Dept XYZ.");
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> result = updateRequest.getPatchOperations();
+    List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     PatchOperation actual = assertSingleResult(result);
 
@@ -261,8 +237,7 @@ public class UpdateRequestTest {
     user2.getName()
          .setFamilyName("Nobody");
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> result = updateRequest.getPatchOperations();
+    List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     PatchOperation actual = assertSingleResult(result);
 
@@ -279,8 +254,7 @@ public class UpdateRequestTest {
                        .equals("work"))
          .forEach(e -> e.setValue("nobody@example.com"));
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> result = updateRequest.getPatchOperations();
+    List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     PatchOperation actual = assertSingleResult(result);
 
@@ -293,8 +267,7 @@ public class UpdateRequestTest {
     ScimUser user2 = createUser();
     user2.setUserName(null);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> result = updateRequest.getPatchOperations();
+    List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     PatchOperation actual = assertSingleResult(result);
 
@@ -307,8 +280,7 @@ public class UpdateRequestTest {
     ScimUser user2 = createUser();
     user2.removeExtension(EnterpriseExtension.class);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> result = updateRequest.getPatchOperations();
+    List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     PatchOperation actual = assertSingleResult(result);
 
@@ -322,9 +294,7 @@ public class UpdateRequestTest {
     user2.getName()
          .setMiddleName(null);
 
-
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> result = updateRequest.getPatchOperations();
+    List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     PatchOperation actual = assertSingleResult(result);
 
@@ -337,8 +307,7 @@ public class UpdateRequestTest {
     ScimUser user2 = createUser();
     user2.setName(null);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> result = updateRequest.getPatchOperations();
+    List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     PatchOperation actual = assertSingleResult(result);
 
@@ -356,8 +325,7 @@ public class UpdateRequestTest {
                                  .collect(Collectors.toList());
     user2.setEmails(newEmails);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> result = updateRequest.getPatchOperations();
+    List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     PatchOperation actual = assertSingleResult(result);
 
@@ -378,8 +346,7 @@ public class UpdateRequestTest {
     
     user1.getAddresses().add(localAddress);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> result = updateRequest.getPatchOperations();
+    List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     PatchOperation actual = assertSingleResult(result);
 
@@ -401,8 +368,7 @@ public class UpdateRequestTest {
     user2.getAddresses().add(localAddress);
     user1.getAddresses().get(0).setPostalCode("01234");
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> result = updateRequest.getPatchOperations();
+    List<PatchOperation> result = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     assertEquals(2, result.size());
 
@@ -422,8 +388,7 @@ public class UpdateRequestTest {
     ext2.setList(new ArrayList<>());
     user2.addExtension(ext2);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> operations = updateRequest.getPatchOperations();
+    List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
     assertTrue(operations.isEmpty(), "Empty Arrays caused a diff");
   }
   
@@ -431,18 +396,19 @@ public class UpdateRequestTest {
   public void verifyEmptyArraysAreNulled() throws Exception {
     ScimUser user1 = createUser();
     ScimUser user2 = createUser();
-    
+
+    PatchGenerator patchGenerator = new PatchGenerator(schemaRegistry);
+
     //Set empty list on root object and verify no differences
     user1.setPhotos(new ArrayList<>());
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> operations = updateRequest.getPatchOperations();
+    List<PatchOperation> operations = patchGenerator.diff(user1, user2);
     assertTrue(operations.isEmpty(), "Empty Arrays are not being nulled out");
     
     //Reset user 1 and empty list on Extension and verify no differences
     user1 = createUser();
     ExampleObjectExtension ext = new ExampleObjectExtension();
     ext.setList(new ArrayList<>());
-    operations = updateRequest.getPatchOperations();
+    operations = patchGenerator.diff(user1, user2);
     assertTrue(operations.isEmpty(), "Empty Arrays are not being nulled out");
     
     //Reset extension and set empty list on element of extension then verify no differences
@@ -450,7 +416,7 @@ public class UpdateRequestTest {
     subobject.setList1(new ArrayList<>());
     ext = new ExampleObjectExtension();
     ext.setSubobject(subobject);
-    operations = updateRequest.getPatchOperations();
+    operations = patchGenerator.diff(user1, user2);
     assertTrue(operations.isEmpty(), "Empty Arrays are not being nulled out");
   }
   
@@ -476,8 +442,7 @@ public class UpdateRequestTest {
     ext2.setList(Stream.of(FIRST,SECOND).collect(Collectors.toList()));
     user2.addExtension(ext2);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> operations = updateRequest.getPatchOperations();
+    List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     assertThat(operations)
       .hasSize(3)
@@ -507,8 +472,7 @@ public class UpdateRequestTest {
     ext2.setList(null);
     user2.addExtension(ext2);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> operations = updateRequest.getPatchOperations();
+    List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
     assertNotNull(operations);
     assertEquals(2, operations.size());
     PatchOperation operation = operations.get(0);
@@ -529,8 +493,7 @@ public class UpdateRequestTest {
     ext2.setList(Stream.of(FIRST,SECOND,FOURTH).collect(Collectors.toList()));
     user2.addExtension(ext2);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> operations = updateRequest.getPatchOperations();
+    List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     assertThat(operations)
       .hasSize(1);
@@ -551,8 +514,7 @@ public class UpdateRequestTest {
     
     user2.getName().setFormatted(nickname);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> operations = updateRequest.getPatchOperations();
+    List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     assertThat(operations).hasSize(2);
     assertThat(operations).filteredOn(op(Type.ADD, "name.formatted", nickname)).hasSize(1);
@@ -571,8 +533,7 @@ public class UpdateRequestTest {
     user1.getName().setFormatted("");
     user2.getName().setFormatted(nickname);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> operations = updateRequest.getPatchOperations();
+    List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     assertThat(operations).hasSize(2);
     assertThat(operations).filteredOn(op(Type.REPLACE, "name.formatted", nickname)).hasSize(1);
@@ -591,8 +552,7 @@ public class UpdateRequestTest {
     user1.getName().setFormatted("");
     user2.getName().setFormatted(nickname);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> operations = updateRequest.getPatchOperations();
+    List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     assertThat(operations).hasSize(2);
     assertThat(operations).filteredOn(op(Type.REPLACE, "name.formatted", nickname)).hasSize(1);
@@ -612,8 +572,7 @@ public class UpdateRequestTest {
     user1.getName().setFormatted(null);
     user2.getName().setFormatted(nickname);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> operations = updateRequest.getPatchOperations();
+    List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     assertThat(operations).hasSize(2);
     assertThat(operations).filteredOn(op(Type.ADD, "name.formatted", nickname)).hasSize(1);
@@ -632,8 +591,7 @@ public class UpdateRequestTest {
     user1.getName().setFormatted(nickname);
     user2.getName().setFormatted(null);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> operations = updateRequest.getPatchOperations();
+    List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     assertThat(operations).hasSize(2);
     assertThat(operations).filteredOn(op(Type.REMOVE, "name.formatted")).hasSize(1);
@@ -654,8 +612,7 @@ public class UpdateRequestTest {
     ext2.setList(list2);
     user2.addExtension(ext2);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> operations = updateRequest.getPatchOperations();
+    List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
     assertEquals(ops.size(), operations.size());
     for(int i = 0; i < operations.size(); i++) {
       PatchOperation actualOp = operations.get(i);
@@ -722,8 +679,7 @@ public class UpdateRequestTest {
     ext2.setList(Stream.of("A","Z").collect(Collectors.toList()));
     user2.addExtension(ext2);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> operations = updateRequest.getPatchOperations();
+    List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
     System.out.println("Number of operations: "+operations.size());
     operations.stream().forEach(op -> System.out.println(op));
 
@@ -749,8 +705,7 @@ public class UpdateRequestTest {
     user1.getName().setFormatted(nickname);
     user2.getName().setFormatted("");
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> operations = updateRequest.getPatchOperations();
+    List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
 
     assertThat(operations).hasSize(2);
     assertThat(operations).filteredOn(op(Type.REPLACE, "name.formatted", "")).hasSize(1);
@@ -777,8 +732,7 @@ public class UpdateRequestTest {
     assertNotNull(workNumber);
     workNumber.setType(null);
 
-    UpdateRequest<ScimUser> updateRequest = new UpdateRequest<>("1234", user1, user2, schemaRegistry);
-    List<PatchOperation> operations = updateRequest.getPatchOperations();
+    List<PatchOperation> operations = new PatchGenerator(schemaRegistry).diff(user1, user2);
     assertNotNull(operations);
 
     System.out.println("Number of operations: "+operations.size());
diff --git a/scim-server-examples/scim-server-jersey/src/main/java/org/apache/directory/scim/example/jersey/service/InMemoryGroupService.java b/scim-server-examples/scim-server-jersey/src/main/java/org/apache/directory/scim/example/jersey/service/InMemoryGroupService.java
index 575e2add..7970486f 100644
--- a/scim-server-examples/scim-server-jersey/src/main/java/org/apache/directory/scim/example/jersey/service/InMemoryGroupService.java
+++ b/scim-server-examples/scim-server-jersey/src/main/java/org/apache/directory/scim/example/jersey/service/InMemoryGroupService.java
@@ -23,15 +23,18 @@ import jakarta.annotation.PostConstruct;
 import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.directory.scim.core.repository.PatchHandler;
+import org.apache.directory.scim.core.repository.PatchHandlerImpl;
 import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
 import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
+import org.apache.directory.scim.spec.exception.ResourceException;
 import org.apache.directory.scim.spec.filter.FilterExpressions;
 import org.apache.directory.scim.spec.filter.FilterResponse;
 import org.apache.directory.scim.spec.filter.Filter;
 import org.apache.directory.scim.spec.filter.PageRequest;
 import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
 import org.apache.directory.scim.spec.resources.ScimExtension;
 import org.apache.directory.scim.spec.resources.ScimGroup;
 
@@ -39,6 +42,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 import java.util.stream.Collectors;
 
@@ -98,9 +102,15 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
   }
 
   @Override
-  public ScimGroup update(UpdateRequest<ScimGroup> updateRequest) throws UnableToUpdateResourceException {
-    String id = updateRequest.getId();
-    ScimGroup resource = updateRequest.getResource();
+  public ScimGroup update(String id, String version, ScimGroup resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    groups.put(id, resource);
+    return resource;
+  }
+
+  @Override
+  public ScimGroup patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    PatchHandler patchHandler = new PatchHandlerImpl(schemaRegistry);
+    ScimGroup resource = patchHandler.apply(get(id), patchOperations);
     groups.put(id, resource);
     return resource;
   }
diff --git a/scim-server-examples/scim-server-jersey/src/main/java/org/apache/directory/scim/example/jersey/service/InMemoryUserService.java b/scim-server-examples/scim-server-jersey/src/main/java/org/apache/directory/scim/example/jersey/service/InMemoryUserService.java
index 24393fcc..f2dab521 100644
--- a/scim-server-examples/scim-server-jersey/src/main/java/org/apache/directory/scim/example/jersey/service/InMemoryUserService.java
+++ b/scim-server-examples/scim-server-jersey/src/main/java/org/apache/directory/scim/example/jersey/service/InMemoryUserService.java
@@ -22,6 +22,7 @@ package org.apache.directory.scim.example.jersey.service;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 import java.util.stream.Collectors;
 
@@ -31,17 +32,20 @@ import jakarta.inject.Inject;
 import jakarta.inject.Named;
 
 import jakarta.ws.rs.core.Response;
+import org.apache.directory.scim.core.repository.PatchHandler;
+import org.apache.directory.scim.core.repository.PatchHandlerImpl;
 import org.apache.directory.scim.example.jersey.extensions.LuckyNumberExtension;
 import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
 import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
+import org.apache.directory.scim.spec.exception.ResourceException;
 import org.apache.directory.scim.spec.extension.EnterpriseExtension;
 import org.apache.directory.scim.spec.filter.FilterExpressions;
 import org.apache.directory.scim.spec.filter.FilterResponse;
 import org.apache.directory.scim.spec.filter.Filter;
 import org.apache.directory.scim.spec.filter.PageRequest;
 import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
 import org.apache.directory.scim.spec.resources.*;
 import org.apache.directory.scim.core.schema.SchemaRegistry;
 
@@ -130,17 +134,21 @@ public class InMemoryUserService implements Repository<ScimUser> {
     return resource;
   }
 
-  /**
-   * @see Repository#update(UpdateRequest)
-   */
   @Override
-  public ScimUser update(UpdateRequest<ScimUser> updateRequest) throws UnableToUpdateResourceException {
-    String id = updateRequest.getId();
-    ScimUser resource = updateRequest.getResource();
+  public ScimUser update(String id, String version, ScimUser resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    users.put(id, resource);
+    return resource;
+  }
+
+  @Override
+  public ScimUser patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    PatchHandler patchHandler = new PatchHandlerImpl(schemaRegistry);
+    ScimUser resource = patchHandler.apply(get(id), patchOperations);
     users.put(id, resource);
     return resource;
   }
 
+
   /**
    * @see Repository#get(java.lang.String)
    */
diff --git a/scim-server-examples/scim-server-memory/src/main/java/org/apache/directory/scim/example/memory/service/InMemoryGroupService.java b/scim-server-examples/scim-server-memory/src/main/java/org/apache/directory/scim/example/memory/service/InMemoryGroupService.java
index 599de035..62ee98c6 100644
--- a/scim-server-examples/scim-server-memory/src/main/java/org/apache/directory/scim/example/memory/service/InMemoryGroupService.java
+++ b/scim-server-examples/scim-server-memory/src/main/java/org/apache/directory/scim/example/memory/service/InMemoryGroupService.java
@@ -23,15 +23,18 @@ import jakarta.annotation.PostConstruct;
 import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.directory.scim.core.repository.PatchHandler;
+import org.apache.directory.scim.core.repository.PatchHandlerImpl;
 import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
 import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
+import org.apache.directory.scim.spec.exception.ResourceException;
 import org.apache.directory.scim.spec.filter.FilterExpressions;
 import org.apache.directory.scim.spec.filter.FilterResponse;
 import org.apache.directory.scim.spec.filter.Filter;
 import org.apache.directory.scim.spec.filter.PageRequest;
 import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
 import org.apache.directory.scim.spec.resources.ScimExtension;
 import org.apache.directory.scim.spec.resources.ScimGroup;
 
@@ -39,6 +42,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 import java.util.stream.Collectors;
 
@@ -98,9 +102,15 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
   }
 
   @Override
-  public ScimGroup update(UpdateRequest<ScimGroup> updateRequest) throws UnableToUpdateResourceException {
-    String id = updateRequest.getId();
-    ScimGroup resource = updateRequest.getResource();
+  public ScimGroup update(String id, String version, ScimGroup resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    groups.put(id, resource);
+    return resource;
+  }
+
+  @Override
+  public ScimGroup patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    PatchHandler patchHandler = new PatchHandlerImpl(schemaRegistry);
+    ScimGroup resource = patchHandler.apply(get(id), patchOperations);
     groups.put(id, resource);
     return resource;
   }
diff --git a/scim-server-examples/scim-server-memory/src/main/java/org/apache/directory/scim/example/memory/service/InMemoryUserService.java b/scim-server-examples/scim-server-memory/src/main/java/org/apache/directory/scim/example/memory/service/InMemoryUserService.java
index 5bef2f5c..02cfaeeb 100644
--- a/scim-server-examples/scim-server-memory/src/main/java/org/apache/directory/scim/example/memory/service/InMemoryUserService.java
+++ b/scim-server-examples/scim-server-memory/src/main/java/org/apache/directory/scim/example/memory/service/InMemoryUserService.java
@@ -22,6 +22,7 @@ package org.apache.directory.scim.example.memory.service;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 import java.util.stream.Collectors;
 
@@ -31,17 +32,20 @@ import jakarta.inject.Inject;
 import jakarta.inject.Named;
 
 import jakarta.ws.rs.core.Response;
+import org.apache.directory.scim.core.repository.PatchHandler;
+import org.apache.directory.scim.core.repository.PatchHandlerImpl;
 import org.apache.directory.scim.example.memory.extensions.LuckyNumberExtension;
 import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
 import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
+import org.apache.directory.scim.spec.exception.ResourceException;
 import org.apache.directory.scim.spec.extension.EnterpriseExtension;
 import org.apache.directory.scim.spec.filter.FilterExpressions;
 import org.apache.directory.scim.spec.filter.FilterResponse;
 import org.apache.directory.scim.spec.filter.Filter;
 import org.apache.directory.scim.spec.filter.PageRequest;
 import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
 import org.apache.directory.scim.spec.resources.*;
 import org.apache.directory.scim.core.schema.SchemaRegistry;
 
@@ -130,13 +134,16 @@ public class InMemoryUserService implements Repository<ScimUser> {
     return resource;
   }
 
-  /**
-   * @see Repository#update(UpdateRequest)
-   */
   @Override
-  public ScimUser update(UpdateRequest<ScimUser> updateRequest) throws UnableToUpdateResourceException {
-    String id = updateRequest.getId();
-    ScimUser resource = updateRequest.getResource();
+  public ScimUser update(String id, String version, ScimUser resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    users.put(id, resource);
+    return resource;
+  }
+
+  @Override
+  public ScimUser patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    PatchHandler patchHandler = new PatchHandlerImpl(schemaRegistry);
+    ScimUser resource = patchHandler.apply(get(id), patchOperations);
     users.put(id, resource);
     return resource;
   }
diff --git a/scim-server-examples/scim-server-quarkus/src/main/java/org/apache/directory/scim/example/quarkus/service/InMemoryGroupService.java b/scim-server-examples/scim-server-quarkus/src/main/java/org/apache/directory/scim/example/quarkus/service/InMemoryGroupService.java
index 486d0845..b582fddd 100644
--- a/scim-server-examples/scim-server-quarkus/src/main/java/org/apache/directory/scim/example/quarkus/service/InMemoryGroupService.java
+++ b/scim-server-examples/scim-server-quarkus/src/main/java/org/apache/directory/scim/example/quarkus/service/InMemoryGroupService.java
@@ -23,15 +23,18 @@ import jakarta.annotation.PostConstruct;
 import jakarta.inject.Inject;
 import jakarta.ws.rs.core.Response;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.directory.scim.core.repository.PatchHandler;
+import org.apache.directory.scim.core.repository.PatchHandlerImpl;
 import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
 import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
+import org.apache.directory.scim.spec.exception.ResourceException;
 import org.apache.directory.scim.spec.filter.FilterExpressions;
 import org.apache.directory.scim.spec.filter.FilterResponse;
 import org.apache.directory.scim.spec.filter.Filter;
 import org.apache.directory.scim.spec.filter.PageRequest;
 import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
 import org.apache.directory.scim.spec.resources.ScimExtension;
 import org.apache.directory.scim.spec.resources.ScimGroup;
 
@@ -39,6 +42,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 import java.util.stream.Collectors;
 
@@ -98,9 +102,15 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
   }
 
   @Override
-  public ScimGroup update(UpdateRequest<ScimGroup> updateRequest) throws UnableToUpdateResourceException {
-    String id = updateRequest.getId();
-    ScimGroup resource = updateRequest.getResource();
+  public ScimGroup update(String id, String version, ScimGroup resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    groups.put(id, resource);
+    return resource;
+  }
+
+  @Override
+  public ScimGroup patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    PatchHandler patchHandler = new PatchHandlerImpl(schemaRegistry);
+    ScimGroup resource = patchHandler.apply(get(id), patchOperations);
     groups.put(id, resource);
     return resource;
   }
diff --git a/scim-server-examples/scim-server-quarkus/src/main/java/org/apache/directory/scim/example/quarkus/service/InMemoryUserService.java b/scim-server-examples/scim-server-quarkus/src/main/java/org/apache/directory/scim/example/quarkus/service/InMemoryUserService.java
index 154ca57d..5112b95b 100644
--- a/scim-server-examples/scim-server-quarkus/src/main/java/org/apache/directory/scim/example/quarkus/service/InMemoryUserService.java
+++ b/scim-server-examples/scim-server-quarkus/src/main/java/org/apache/directory/scim/example/quarkus/service/InMemoryUserService.java
@@ -19,10 +19,7 @@
 
 package org.apache.directory.scim.example.quarkus.service;
 
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
+import java.util.*;
 import java.util.stream.Collectors;
 
 import jakarta.annotation.PostConstruct;
@@ -31,17 +28,20 @@ import jakarta.inject.Inject;
 import jakarta.inject.Named;
 
 import jakarta.ws.rs.core.Response;
+import org.apache.directory.scim.core.repository.PatchHandler;
+import org.apache.directory.scim.core.repository.PatchHandlerImpl;
 import org.apache.directory.scim.example.quarkus.extensions.LuckyNumberExtension;
 import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
 import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
+import org.apache.directory.scim.spec.exception.ResourceException;
 import org.apache.directory.scim.spec.extension.EnterpriseExtension;
 import org.apache.directory.scim.spec.filter.FilterExpressions;
 import org.apache.directory.scim.spec.filter.FilterResponse;
 import org.apache.directory.scim.spec.filter.Filter;
 import org.apache.directory.scim.spec.filter.PageRequest;
 import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
 import org.apache.directory.scim.spec.resources.*;
 import org.apache.directory.scim.core.schema.SchemaRegistry;
 
@@ -130,13 +130,16 @@ public class InMemoryUserService implements Repository<ScimUser> {
     return resource;
   }
 
-  /**
-   * @see Repository#update(UpdateRequest)
-   */
   @Override
-  public ScimUser update(UpdateRequest<ScimUser> updateRequest) throws UnableToUpdateResourceException {
-    String id = updateRequest.getId();
-    ScimUser resource = updateRequest.getResource();
+  public ScimUser update(String id, String version, ScimUser resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    users.put(id, resource);
+    return resource;
+  }
+
+  @Override
+  public ScimUser patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    PatchHandler patchHandler = new PatchHandlerImpl(schemaRegistry);
+    ScimUser resource = patchHandler.apply(get(id), patchOperations);
     users.put(id, resource);
     return resource;
   }
diff --git a/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryGroupService.java b/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryGroupService.java
index 5498f6fd..13a8de03 100644
--- a/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryGroupService.java
+++ b/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryGroupService.java
@@ -21,12 +21,15 @@ package org.apache.directory.scim.example.spring.service;
 
 import jakarta.annotation.PostConstruct;
 import jakarta.ws.rs.core.Response;
+import org.apache.directory.scim.core.repository.PatchHandler;
+import org.apache.directory.scim.core.repository.PatchHandlerImpl;
 import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
 import org.apache.directory.scim.core.schema.SchemaRegistry;
 import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
+import org.apache.directory.scim.spec.exception.ResourceException;
 import org.apache.directory.scim.spec.filter.*;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
 import org.apache.directory.scim.spec.resources.ScimExtension;
 import org.apache.directory.scim.spec.resources.ScimGroup;
 
@@ -34,6 +37,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 import java.util.stream.Collectors;
 
@@ -88,9 +92,15 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
   }
 
   @Override
-  public ScimGroup update(UpdateRequest<ScimGroup> updateRequest) throws UnableToUpdateResourceException {
-    String id = updateRequest.getId();
-    ScimGroup resource = updateRequest.getResource();
+  public ScimGroup update(String id, String version, ScimGroup resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    groups.put(id, resource);
+    return resource;
+  }
+
+  @Override
+  public ScimGroup patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    PatchHandler patchHandler = new PatchHandlerImpl(schemaRegistry);
+    ScimGroup resource = patchHandler.apply(get(id), patchOperations);
     groups.put(id, resource);
     return resource;
   }
diff --git a/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryUserService.java b/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryUserService.java
index dca649c8..210cfc25 100644
--- a/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryUserService.java
+++ b/scim-server-examples/scim-server-spring-boot/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryUserService.java
@@ -22,20 +22,24 @@ package org.apache.directory.scim.example.spring.service;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 import java.util.stream.Collectors;
 
 import jakarta.annotation.PostConstruct;
 
 import jakarta.ws.rs.core.Response;
+import org.apache.directory.scim.core.repository.PatchHandler;
+import org.apache.directory.scim.core.repository.PatchHandlerImpl;
 import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
 import org.apache.directory.scim.core.schema.SchemaRegistry;
 import org.apache.directory.scim.example.spring.extensions.LuckyNumberExtension;
 import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
+import org.apache.directory.scim.spec.exception.ResourceException;
 import org.apache.directory.scim.spec.extension.EnterpriseExtension;
 import org.apache.directory.scim.spec.filter.*;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
 import org.apache.directory.scim.spec.resources.*;
 import org.springframework.stereotype.Service;
 
@@ -120,13 +124,16 @@ public class InMemoryUserService implements Repository<ScimUser> {
     return resource;
   }
 
-  /**
-   * @see Repository#update(UpdateRequest)
-   */
   @Override
-  public ScimUser update(UpdateRequest<ScimUser> updateRequest) throws UnableToUpdateResourceException {
-    String id = updateRequest.getId();
-    ScimUser resource = updateRequest.getResource();
+  public ScimUser update(String id, String version, ScimUser resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    users.put(id, resource);
+    return resource;
+  }
+
+  @Override
+  public ScimUser patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    PatchHandler patchHandler = new PatchHandlerImpl(schemaRegistry);
+    ScimUser resource = patchHandler.apply(get(id), patchOperations);
     users.put(id, resource);
     return resource;
   }
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/BaseResourceTypeResourceImpl.java b/scim-server/src/main/java/org/apache/directory/scim/server/rest/BaseResourceTypeResourceImpl.java
index 39dd8260..d1965b30 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/BaseResourceTypeResourceImpl.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/rest/BaseResourceTypeResourceImpl.java
@@ -25,10 +25,8 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
-import java.util.function.Function;
 
 import jakarta.enterprise.inject.spi.CDI;
-import jakarta.ws.rs.WebApplicationException;
 import jakarta.ws.rs.core.*;
 import jakarta.ws.rs.core.Response.ResponseBuilder;
 import jakarta.ws.rs.core.Response.Status;
@@ -40,10 +38,10 @@ import org.apache.directory.scim.core.repository.RepositoryRegistry;
 import org.apache.directory.scim.core.repository.Repository;
 import org.apache.directory.scim.core.schema.SchemaRegistry;
 import org.apache.directory.scim.spec.exception.ResourceException;
+import org.apache.directory.scim.spec.schema.Meta;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import org.apache.directory.scim.core.repository.UpdateRequest;
 import org.apache.directory.scim.core.repository.annotations.ScimProcessingExtension;
 import org.apache.directory.scim.core.repository.extensions.AttributeFilterExtension;
 import org.apache.directory.scim.core.repository.extensions.ProcessingExtension;
@@ -53,7 +51,6 @@ import org.apache.directory.scim.protocol.adapter.FilterWrapper;
 import org.apache.directory.scim.protocol.BaseResourceTypeResource;
 import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
 import org.apache.directory.scim.spec.filter.attribute.AttributeReferenceListWrapper;
-import org.apache.directory.scim.protocol.data.ErrorResponse;
 import org.apache.directory.scim.protocol.data.ListResponse;
 import org.apache.directory.scim.protocol.data.PatchRequest;
 import org.apache.directory.scim.protocol.data.SearchRequest;
@@ -70,14 +67,10 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
 
   private static final Logger LOG = LoggerFactory.getLogger(BaseResourceTypeResourceImpl.class);
 
-  private final SchemaRegistry schemaRegistry;
-
   private final RepositoryRegistry repositoryRegistry;
 
   private final  AttributeUtil attributeUtil;
 
-  private final  EtagGenerator etagGenerator;
-
   private final Class<T> resourceClass;
 
   // TODO: Field injection of UriInfo, Request should work with all implementations
@@ -88,10 +81,11 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
   @Context
   Request request;
 
-  public BaseResourceTypeResourceImpl(SchemaRegistry schemaRegistry, RepositoryRegistry repositoryRegistry, EtagGenerator etagGenerator, Class<T> resourceClass) {
-    this.schemaRegistry = schemaRegistry;
+  @Context
+  HttpHeaders headers;
+
+  public BaseResourceTypeResourceImpl(SchemaRegistry schemaRegistry, RepositoryRegistry repositoryRegistry, Class<T> resourceClass) {
     this.repositoryRegistry = repositoryRegistry;
-    this.etagGenerator = etagGenerator;
     this.resourceClass = resourceClass;
     this.attributeUtil = new AttributeUtil(schemaRegistry);
   }
@@ -126,10 +120,13 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
       }
     }
 
-    if (resource != null) {
-      EntityTag backingETag = requireEtag(resource);
-      ResponseBuilder evaluatePreconditionsResponse = request.evaluatePreconditions(backingETag);
+    if (resource == null) {
+      throw notFoundException(id);
+    }
 
+    EntityTag etag = fromVersion(resource);
+    if (etag != null) {
+      ResponseBuilder evaluatePreconditionsResponse = request.evaluatePreconditions(etag);
       if (evaluatePreconditionsResponse != null) {
         return Response.status(Status.NOT_MODIFIED).build();
       }
@@ -139,12 +136,6 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
     Set<AttributeReference> excludedAttributeReferences = AttributeReferenceListWrapper.getAttributeReferences(excludedAttributes);
     validateAttributes(attributeReferences, excludedAttributeReferences);
 
-    if (resource == null) {
-      throw notFoundException(id);
-    }
-
-    EntityTag etag = requireEtag(resource);
-
     // Process Attributes
     resource = processFilterAttributeExtensions(repository, resource, attributeReferences, excludedAttributeReferences);
     resource = attributesForDisplayThrowOnError(resource, attributeReferences, excludedAttributeReferences);
@@ -186,7 +177,7 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
 
     T created = repository.create(resource);
 
-    EntityTag etag = etag(created);
+    EntityTag etag = fromVersion(created);
 
     // Process Attributes
     created = processFilterAttributeExtensions(repository, created, attributeReferences, excludedAttributeReferences);
@@ -195,10 +186,6 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
       created = attributesForDisplay(created, attributeReferences, excludedAttributeReferences);
     } catch (AttributeException e) {
         log.debug("Exception thrown while processing attributes", e);
-        return Response.status(Status.CREATED)
-                .location(buildLocationTag(created))
-                .tag(etag)
-                .build();
     }
 
     return Response.status(Status.CREATED)
@@ -244,7 +231,6 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
       List<T> results = new ArrayList<>();
 
       for (T resource : filterResp.getResources()) {
-        requireEtag(resource);
 
         // Process Attributes
         resource = processFilterAttributeExtensions(repository, resource, attributeReferences, excludedAttributeReferences);
@@ -262,15 +248,14 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
 
   @Override
   public Response update(T resource, String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
-    return update(id, attributes, excludedAttributes, (stored) ->
-      new UpdateRequest<>(id, stored, resource, schemaRegistry));
+    return update(attributes, excludedAttributes, (etag, includeAttributes, excludeAttributes, repository)
+      -> repository.update(id, etag,resource, includeAttributes, excludeAttributes));
   }
 
   @Override
   public Response patch(PatchRequest patchRequest, String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException, ResourceException {
-
-    return update(id, attributes, excludedAttributes, (stored) ->
-      new UpdateRequest<>(id, stored, patchRequest.getPatchOperationList(), schemaRegistry));
+    return update(attributes, excludedAttributes, (etag, includeAttributes, excludeAttributes, repository)
+      -> repository.patch(id, etag, patchRequest.getPatchOperationList(), includeAttributes, excludeAttributes));
   }
 
   @Override
@@ -281,7 +266,7 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
         .build();
   }
 
-  private Response update(String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes, Function<T, UpdateRequest<T>> updateRequestFunction) throws ScimException, ResourceException {
+  private Response update(AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes, UpdateFunction<T> updateFunction) throws ScimException, ResourceException {
 
     Repository<T> repository = getRepositoryInternal();
 
@@ -289,23 +274,14 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
     Set<AttributeReference> excludedAttributeReferences = AttributeReferenceListWrapper.getAttributeReferences(excludedAttributes);
     validateAttributes(attributeReferences, excludedAttributeReferences);
 
-    T stored = repository.get(id);
-
-    if (stored == null) {
-      throw notFoundException(id);
-    }
-
-    EntityTag backingETag = requireEtag(stored);
-    validatePreconditions(id, backingETag);
-
-    UpdateRequest<T> updateRequest = updateRequestFunction.apply(stored);
-    T updated = repository.update(updateRequest);
+    String requestEtag = headers.getHeaderString("ETag");
+    T updated = updateFunction.update(requestEtag, attributeReferences, excludedAttributeReferences, repository);
 
     // Process Attributes
     updated = processFilterAttributeExtensions(repository, updated, attributeReferences, excludedAttributeReferences);
     updated = attributesForDisplayIgnoreErrors(updated, attributeReferences, excludedAttributeReferences);
 
-    EntityTag etag = etag(updated);
+    EntityTag etag = fromVersion(updated);
     return Response.ok(updated)
       .location(buildLocationTag(updated))
       .tag(etag)
@@ -382,35 +358,51 @@ public abstract class BaseResourceTypeResourceImpl<T extends ScimResource> imple
     return new ScimException(Status.NOT_FOUND, "Resource " + id + " not found");
   }
 
-  private void validatePreconditions(String id, EntityTag etag) {
-    ResponseBuilder response = request.evaluatePreconditions(etag);
-    if (response != null) {
-      throw new WebApplicationException(response
-        .entity(new ErrorResponse(Status.PRECONDITION_FAILED, "Failed to update record, backing record has changed - " + id))
-        .build());
-    }
-  }
+//  private void validatePreconditions(String id, EntityTag etag) {
+//    ResponseBuilder response = request.evaluatePreconditions(etag);
+//    if (response != null) {
+//      throw new WebApplicationException(response
+//        .entity(new ErrorResponse(Status.PRECONDITION_FAILED, "Failed to update record, backing record has changed - " + id))
+//        .build());
+//    }
+//  }
+//
+//  private EntityTag requireEtag(ScimResource resource) throws ScimException {
+//    try {
+//      return etagGenerator.generateEtag(resource);
+//    } catch (EtagGenerationException e) {
+//      throw new ScimException(Status.INTERNAL_SERVER_ERROR, "Failed to generate the etag", e);
+//    }
+//  }
+//
+//  private EntityTag etag(ScimResource resource) {
+//    try {
+//      return etagGenerator.generateEtag(resource);
+//    } catch (EtagGenerationException e) {
+//      log.warn("Failed to generate etag for resource", e);
+//      return null;
+//    }
+//  }
 
-  private EntityTag requireEtag(ScimResource resource) throws ScimException {
-    try {
-      return etagGenerator.generateEtag(resource);
-    } catch (EtagGenerationException e) {
-      throw new ScimException(Status.INTERNAL_SERVER_ERROR, "Failed to generate the etag", e);
+  private void validateAttributes(Set<AttributeReference> attributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ScimException {
+    if (!attributeReferences.isEmpty() && !excludedAttributeReferences.isEmpty()) {
+      throw new ScimException(Status.BAD_REQUEST, "Cannot include both attributes and excluded attributes in a single request");
     }
   }
 
-  private EntityTag etag(ScimResource resource) {
-    try {
-      return etagGenerator.generateEtag(resource);
-    } catch (EtagGenerationException e) {
-      log.warn("Failed to generate etag for resource", e);
-      return null;
+  private EntityTag fromVersion(ScimResource resource) {
+    Meta meta = resource.getMeta();
+    if (meta != null) {
+      String version = meta.getVersion();
+      if (version != null) {
+        return new EntityTag(version);
+      }
     }
+    return null;
   }
 
-  private void validateAttributes(Set<AttributeReference> attributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ScimException {
-    if (!attributeReferences.isEmpty() && !excludedAttributeReferences.isEmpty()) {
-      throw new ScimException(Status.BAD_REQUEST, "Cannot include both attributes and excluded attributes in a single request");
-    }
+  @FunctionalInterface
+  private interface UpdateFunction<T extends ScimResource> {
+    T update(String etag, Set<AttributeReference> includeAttributes, Set<AttributeReference> excludeAttributes, Repository<T> repository) throws ResourceException;
   }
 }
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/BulkResourceImpl.java b/scim-server/src/main/java/org/apache/directory/scim/server/rest/BulkResourceImpl.java
index 4d1291a0..72c9155e 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/BulkResourceImpl.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/rest/BulkResourceImpl.java
@@ -36,7 +36,6 @@ import org.apache.directory.scim.server.exception.UnableToRetrieveResourceExcept
 import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
 import org.apache.directory.scim.core.repository.Repository;
 import org.apache.directory.scim.core.repository.RepositoryRegistry;
-import org.apache.directory.scim.core.repository.UpdateRequest;
 import org.apache.directory.scim.protocol.BulkResource;
 import org.apache.directory.scim.protocol.data.BulkOperation;
 import org.apache.directory.scim.protocol.data.BulkOperation.Method;
@@ -288,10 +287,7 @@ public class BulkResourceImpl implements BulkResource {
         Class<ScimResource> scimResourceClass = (Class<ScimResource>) scimResource.getClass();
         Repository<ScimResource> repository = repositoryRegistry.getRepository(scimResourceClass);
 
-        ScimResource original = repository.get(scimResourceId);
-
-        UpdateRequest<ScimResource> updateRequest = new UpdateRequest<>(scimResourceId, original, scimResource, schemaRegistry);
-        repository.update(updateRequest);
+        repository.update(scimResourceId, null, scimResource, Collections.emptySet(), Collections.emptySet());
       } catch (UnresolvableOperationException unresolvableOperationException) {
         log.error("Could not complete final resolution pass, unresolvable bulkId", unresolvableOperationException);
 
@@ -464,11 +460,7 @@ public class BulkResourceImpl implements BulkResource {
                                      + 1);
 
       try {
-        ScimResource original = repository.get(id);
-
-        UpdateRequest<ScimResource> updateRequest = new UpdateRequest<>(id, original, scimResource, schemaRegistry);
-        
-        repository.update(updateRequest);
+        repository.update(id, null, scimResource, Collections.emptySet(), Collections.emptySet());
         operationResult.setStatus(StatusWrapper.wrap(Status.OK));
       } catch (UnableToRetrieveResourceException e) {
         operationResult.setStatus(StatusWrapper.wrap(Status.NOT_FOUND));
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/GroupResourceImpl.java b/scim-server/src/main/java/org/apache/directory/scim/server/rest/GroupResourceImpl.java
index 1384204c..bcdeb1e1 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/GroupResourceImpl.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/rest/GroupResourceImpl.java
@@ -35,12 +35,12 @@ import org.apache.directory.scim.core.schema.SchemaRegistry;
 public class GroupResourceImpl extends BaseResourceTypeResourceImpl<ScimGroup> implements GroupResource {
 
   @Inject
-  public GroupResourceImpl(SchemaRegistry schemaRegistry, RepositoryRegistry repositoryRegistry, EtagGenerator etagGenerator) {
-    super(schemaRegistry, repositoryRegistry, etagGenerator, ScimGroup.class);
+  public GroupResourceImpl(SchemaRegistry schemaRegistry, RepositoryRegistry repositoryRegistry) {
+    super(schemaRegistry, repositoryRegistry, ScimGroup.class);
   }
 
   public GroupResourceImpl() {
     // CDI
-    this(null, null, null);
+    this(null, null);
   }
 }
diff --git a/scim-server/src/main/java/org/apache/directory/scim/server/rest/UserResourceImpl.java b/scim-server/src/main/java/org/apache/directory/scim/server/rest/UserResourceImpl.java
index 75bebe21..963f43ca 100644
--- a/scim-server/src/main/java/org/apache/directory/scim/server/rest/UserResourceImpl.java
+++ b/scim-server/src/main/java/org/apache/directory/scim/server/rest/UserResourceImpl.java
@@ -39,12 +39,12 @@ import org.apache.directory.scim.core.schema.SchemaRegistry;
 public class UserResourceImpl extends BaseResourceTypeResourceImpl<ScimUser> implements UserResource {
 
   @Inject
-  public UserResourceImpl(SchemaRegistry schemaRegistry, RepositoryRegistry repositoryRegistry, EtagGenerator etagGenerator) {
-    super(schemaRegistry, repositoryRegistry, etagGenerator, ScimUser.class);
+  public UserResourceImpl(SchemaRegistry schemaRegistry, RepositoryRegistry repositoryRegistry) {
+    super(schemaRegistry, repositoryRegistry, ScimUser.class);
   }
 
   public UserResourceImpl() {
     // CDI
-    this(null, null, null);
+    this(null, null);
   }
 }
diff --git a/scim-server/src/test/java/org/apache/directory/scim/server/it/testapp/InMemoryGroupService.java b/scim-server/src/test/java/org/apache/directory/scim/server/it/testapp/InMemoryGroupService.java
index b4de6ee0..672390fa 100644
--- a/scim-server/src/test/java/org/apache/directory/scim/server/it/testapp/InMemoryGroupService.java
+++ b/scim-server/src/test/java/org/apache/directory/scim/server/it/testapp/InMemoryGroupService.java
@@ -25,24 +25,23 @@ import jakarta.inject.Inject;
 import jakarta.inject.Named;
 import jakarta.ws.rs.core.Response;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.directory.scim.core.repository.PatchHandler;
+import org.apache.directory.scim.core.repository.PatchHandlerImpl;
 import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
 import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
+import org.apache.directory.scim.spec.exception.ResourceException;
 import org.apache.directory.scim.spec.filter.FilterExpressions;
 import org.apache.directory.scim.spec.filter.FilterResponse;
 import org.apache.directory.scim.spec.filter.Filter;
 import org.apache.directory.scim.spec.filter.PageRequest;
 import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
 import org.apache.directory.scim.spec.resources.ScimExtension;
 import org.apache.directory.scim.spec.resources.ScimGroup;
 import org.apache.directory.scim.core.schema.SchemaRegistry;
 
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
+import java.util.*;
 import java.util.stream.Collectors;
 
 @Named
@@ -97,9 +96,15 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
   }
 
   @Override
-  public ScimGroup update(UpdateRequest<ScimGroup> updateRequest) throws UnableToUpdateResourceException {
-    String id = updateRequest.getId();
-    ScimGroup resource = updateRequest.getResource();
+  public ScimGroup update(String id, String version, ScimGroup resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    groups.put(id, resource);
+    return resource;
+  }
+
+  @Override
+  public ScimGroup patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    PatchHandler patchHandler = new PatchHandlerImpl(schemaRegistry);
+    ScimGroup resource = patchHandler.apply(get(id), patchOperations);
     groups.put(id, resource);
     return resource;
   }
diff --git a/scim-server/src/test/java/org/apache/directory/scim/server/it/testapp/InMemoryUserService.java b/scim-server/src/test/java/org/apache/directory/scim/server/it/testapp/InMemoryUserService.java
index f9d29f3c..a053cbb7 100644
--- a/scim-server/src/test/java/org/apache/directory/scim/server/it/testapp/InMemoryUserService.java
+++ b/scim-server/src/test/java/org/apache/directory/scim/server/it/testapp/InMemoryUserService.java
@@ -24,21 +24,25 @@ import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 import jakarta.inject.Named;
 import jakarta.ws.rs.core.Response;
+import org.apache.directory.scim.core.repository.PatchHandler;
+import org.apache.directory.scim.core.repository.PatchHandlerImpl;
 import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
 import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
+import org.apache.directory.scim.spec.exception.ResourceException;
 import org.apache.directory.scim.spec.filter.FilterExpressions;
 import org.apache.directory.scim.spec.filter.FilterResponse;
 import org.apache.directory.scim.spec.filter.Filter;
 import org.apache.directory.scim.spec.filter.PageRequest;
 import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
 import org.apache.directory.scim.spec.resources.*;
 import org.apache.directory.scim.core.schema.SchemaRegistry;
 
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -123,13 +127,16 @@ public class InMemoryUserService implements Repository<ScimUser> {
     return resource;
   }
 
-  /**
-   * @see Repository#update(UpdateRequest)
-   */
   @Override
-  public ScimUser update(UpdateRequest<ScimUser> updateRequest) throws UnableToUpdateResourceException {
-    String id = updateRequest.getId();
-    ScimUser resource = updateRequest.getResource();
+  public ScimUser update(String id, String version, ScimUser resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    users.put(id, resource);
+    return resource;
+  }
+
+  @Override
+  public ScimUser patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    PatchHandler patchHandler = new PatchHandlerImpl(schemaRegistry);
+    ScimUser resource = patchHandler.apply(get(id), patchOperations);
     users.put(id, resource);
     return resource;
   }
diff --git a/support/spring-boot/src/test/java/org/apache/directory/scim/spring/it/app/InMemoryGroupService.java b/support/spring-boot/src/test/java/org/apache/directory/scim/spring/it/app/InMemoryGroupService.java
index 688367f1..2c0f121e 100644
--- a/support/spring-boot/src/test/java/org/apache/directory/scim/spring/it/app/InMemoryGroupService.java
+++ b/support/spring-boot/src/test/java/org/apache/directory/scim/spring/it/app/InMemoryGroupService.java
@@ -22,12 +22,15 @@ package org.apache.directory.scim.spring.it.app;
 import jakarta.annotation.PostConstruct;
 import jakarta.ws.rs.core.Response;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.directory.scim.core.repository.PatchHandler;
+import org.apache.directory.scim.core.repository.PatchHandlerImpl;
 import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
 import org.apache.directory.scim.core.schema.SchemaRegistry;
 import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
+import org.apache.directory.scim.spec.exception.ResourceException;
 import org.apache.directory.scim.spec.filter.*;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
 import org.apache.directory.scim.spec.resources.ScimExtension;
 import org.apache.directory.scim.spec.resources.ScimGroup;
 import org.springframework.stereotype.Service;
@@ -36,6 +39,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 import java.util.stream.Collectors;
 
@@ -87,9 +91,15 @@ public class InMemoryGroupService implements Repository<ScimGroup> {
   }
 
   @Override
-  public ScimGroup update(UpdateRequest<ScimGroup> updateRequest) throws UnableToUpdateResourceException {
-    String id = updateRequest.getId();
-    ScimGroup resource = updateRequest.getResource();
+  public ScimGroup update(String id, String version, ScimGroup resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    groups.put(id, resource);
+    return resource;
+  }
+
+  @Override
+  public ScimGroup patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    PatchHandler patchHandler = new PatchHandlerImpl(schemaRegistry);
+    ScimGroup resource = patchHandler.apply(get(id), patchOperations);
     groups.put(id, resource);
     return resource;
   }
diff --git a/support/spring-boot/src/test/java/org/apache/directory/scim/spring/it/app/InMemoryUserService.java b/support/spring-boot/src/test/java/org/apache/directory/scim/spring/it/app/InMemoryUserService.java
index 51b50b64..498e4f6e 100644
--- a/support/spring-boot/src/test/java/org/apache/directory/scim/spring/it/app/InMemoryUserService.java
+++ b/support/spring-boot/src/test/java/org/apache/directory/scim/spring/it/app/InMemoryUserService.java
@@ -21,12 +21,15 @@ package org.apache.directory.scim.spring.it.app;
 
 import jakarta.annotation.PostConstruct;
 import jakarta.ws.rs.core.Response;
+import org.apache.directory.scim.core.repository.PatchHandler;
+import org.apache.directory.scim.core.repository.PatchHandlerImpl;
 import org.apache.directory.scim.core.repository.Repository;
-import org.apache.directory.scim.core.repository.UpdateRequest;
 import org.apache.directory.scim.core.schema.SchemaRegistry;
 import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
-import org.apache.directory.scim.server.exception.UnableToUpdateResourceException;
+import org.apache.directory.scim.spec.exception.ResourceException;
 import org.apache.directory.scim.spec.filter.*;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
 import org.apache.directory.scim.spec.resources.*;
 import org.springframework.stereotype.Service;
 
@@ -34,6 +37,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -117,13 +121,16 @@ public class InMemoryUserService implements Repository<ScimUser> {
     return resource;
   }
 
-  /**
-   * @see Repository#update(UpdateRequest)
-   */
   @Override
-  public ScimUser update(UpdateRequest<ScimUser> updateRequest) throws UnableToUpdateResourceException {
-    String id = updateRequest.getId();
-    ScimUser resource = updateRequest.getResource();
+  public ScimUser update(String id, String version, ScimUser resource, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    users.put(id, resource);
+    return resource;
+  }
+
+  @Override
+  public ScimUser patch(String id, String version, List<PatchOperation> patchOperations, Set<AttributeReference> includedAttributeReferences, Set<AttributeReference> excludedAttributeReferences) throws ResourceException {
+    PatchHandler patchHandler = new PatchHandlerImpl(schemaRegistry);
+    ScimUser resource = patchHandler.apply(get(id), patchOperations);
     users.put(id, resource);
     return resource;
   }