{"id":23662800,"url":"https://github.com/cassiomolin/http-patch-spring","last_synced_at":"2025-09-01T17:31:29.441Z","repository":{"id":53285658,"uuid":"190644022","full_name":"cassiomolin/http-patch-spring","owner":"cassiomolin","description":"Using HTTP PATCH in Spring.","archived":false,"fork":false,"pushed_at":"2021-03-31T21:21:26.000Z","size":251,"stargazers_count":37,"open_issues_count":1,"forks_count":12,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-12-20T08:49:14.913Z","etag":null,"topics":["http-patch","rest","rest-api","spring","spring-boot"],"latest_commit_sha":null,"homepage":"https://cassiomolin.com/using-http-patch-in-spring/","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cassiomolin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-06-06T20:16:25.000Z","updated_at":"2024-07-10T11:37:41.000Z","dependencies_parsed_at":"2022-08-28T16:41:40.770Z","dependency_job_id":null,"html_url":"https://github.com/cassiomolin/http-patch-spring","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cassiomolin%2Fhttp-patch-spring","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cassiomolin%2Fhttp-patch-spring/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cassiomolin%2Fhttp-patch-spring/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cassiomolin%2Fhttp-patch-spring/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cassiomolin","download_url":"https://codeload.github.com/cassiomolin/http-patch-spring/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":231701542,"owners_count":18413421,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http-patch","rest","rest-api","spring","spring-boot"],"created_at":"2024-12-29T05:14:24.004Z","updated_at":"2024-12-29T05:14:24.554Z","avatar_url":"https://github.com/cassiomolin.png","language":"Java","readme":"# Using HTTP `PATCH` in Spring\n\nThis project demonstrates an approach to support HTTP `PATCH` with _JSON Patch_ and _JSON Merge Patch_ for performing partial modifications to resources in Spring, as described in my [blog][blog.post]. I also have put together a [Postman collection][repo.postman] so you can play around with the API.\n\nAs I have seen lots of misunderstanding on how `PATCH` works, I aim to clarify its usage before diving into the actual solution.\n\n##### Table of Contents  \n- [The problem with `PUT` and the need for `PATCH`](#the-problem-with-put-and-the-need-for-patch)  \n- [Describing how the resource will be modified](#describing-how-the-resource-will-be-modified)\n  - [JSON Patch](#json-patch)\n  - [JSON Merge Patch](#json-merge-patch)\n- [JSON-P: Java API for JSON Processing](#json-p-java-api-for-json-processing)\n- [Parsing the request payload](#parsing-the-request-payload)\n- [Creating the controller methods](#creating-the-controller-methods)\n- [Applying the patch](#applying-the-patch)\n- [Validating the patch](#validating-the-patch)\n- [Bonus: Decoupling the domain model from the API model](#bonus-decoupling-the-domain-model-from-the-api-model)\n- [References](#references)\n\n## The problem with `PUT` and the need for `PATCH`\n\nConsider, for example, we are creating an API to manage contacts. On the server, we have a resource that can be represented with the following JSON document:\n\n```json\n{\n  \"id\": 1,\n  \"name\": \"John Appleseed\",\n  \"work\": {\n    \"title\": \"Engineer\",\n    \"company\": \"Acme\"\n  },\n  \"phones\": [\n    {\n      \"phone\": \"0000000000\",\n      \"type\": \"mobile\"\n    }\n  ]\n}\n```\n\nLet's say that John has been promoted to senior engineer and we want to keep our contact list updated. We could modify this resource using a `PUT` request, as shown below:\n\n```http\nPUT /contacts/1 HTTP/1.1\nHost: example.org\nContent-Type: application/json\n\n{\n  \"id\": 1,\n  \"name\": \"John Appleseed\",\n  \"work\": {\n    \"title\": \"Senior Engineer\",\n    \"company\": \"Acme\"\n  },\n  \"phones\": [\n    {\n      \"phone\": \"0000000000\",\n      \"type\": \"mobile\"\n    }\n  ]\n}\n```\n\nWith `PUT`, however, we have to send the full representation of the resource even when we need to modify a _single_ field of a resource, which may not be desirable in some situations.\n\nLet's have a look on how the `PUT` HTTP method is defined in the [RFC 7231][rfc7231], one of the documents that currently define the HTTP/1.1 protocol:\n\n\u003e[**4.3.4.  PUT**][rfc7231.put]\n\u003e\n\u003eThe `PUT` method requests that the state of the target resource be created or replaced with the state defined by the representation enclosed in the request message payload. [...]\n\nSo, as per definition, the `PUT` method is meant to be used for:\n\n- _Creating_ resources \u003csup name=\"a1\"\u003e[[1]](#f1)\u003c/sup\u003e\n- _Replacing_ the state of a given resource\n\nThe key here is: the `PUT` payload must be a _new representation of the resource_. Hence it's not meant for performing _partial modifications_ to resources at all. To fill this gap, the `PATCH` method was created and it is currently defined in the [RFC 5789][rfc5789]:\n\n\u003e [**2. The PATCH Method**][rfc5789.patch]\n\u003e\n\u003eThe `PATCH` method requests that a set of changes described in the request entity be applied to the resource identified by the Request-URI. The set of changes is represented in a format called a \"patch document\" identified by a media type. [...]\n\nThe difference between the `PUT` and `PATCH` requests is reflected in the way the server processes the request payload to modify a given resource:\n\n- In a `PUT` request, the payload is a modified version of the resource stored on the server. And the client is requesting the stored version to be _replaced_ with the new version.\n- In a `PATCH` request, the request payload contains a _set of instructions_ describing how a resource currently stored on the server should be modified to produce a new version.\n\n## Describing how the resource will be modified\n\nThe `PATCH` method definition, however, doesn't enforce any format for the request payload apart from mentioning that the request payload should contain a set of instructions describing how the resource will be modified and that set of instructions is identified by a media type.\n\nLet's have a look at some formats for describing how a resource is to be `PATCH`ed:\n\n### JSON Patch\n\nJSON Patch is a format for expressing a sequence of operations to be applied to a JSON document. It is defined in the [RFC 6902][rfc6902] and is identified by the `application/json-patch+json` media type.\n\nThe JSON Patch document represents an array of objects and each object represents a single operation to be applied to the target JSON document. \n\nThe evaluation of a JSON Patch document begins against a target JSON document and the operations are applied sequentially in the order they appear in the array. Each operation in the sequence is applied to the target document and the resulting document becomes the target of the next operation. The evaluation continues until all operations are successfully applied or until an error condition is encountered.\n\nThe operation objects must have exactly one `op` member, whose value indicates the operation to perform:\n\n| Operation | Description |\n| --------- | ----------- |\n| [`add`][rfc6902.add] | Adds the value at the target location; if the value exists in the given location, it's replaced |\n| [`remove`][rfc6902.remove] | Removes the value at the target location |\n| [`replace`][rfc6902.replace] | Replaces the value at the target location |\n| [`move`][rfc6902.move] | Removes the value at a specified location and adds it to the target location |\n| [`copy`][rfc6902.copy] | Copies the value at a specified location to the target location |\n| [`test`][rfc6902.test] | Tests that a value at the target location is equal to a specified value |\n\nAny other values are considered errors.\n\nA request to modify John's job title could be:\n\n```http\nPATCH /contacts/1 HTTP/1.1\nHost: example.org\nContent-Type: application/json-patch+json\n\n[\n  { \"op\": \"replace\", \"path\": \"/work/title\", \"value\": \"Senior Engineer\" }\n]\n```\n\n### JSON Merge Patch\n\nJSON Merge Patch is a format that describes the changes to be made to a target JSON document using a syntax that closely mimics the document being modified. It is defined in the [RFC 7396][rfc7396] is identified by the `application/merge-patch+json` media type.\n\nThe server processing a JSON Merge Patch document determine the exact set of changes being requested by comparing the content of the provided patch against the current content of the target document: \n\n- If the merge patch contains members that do not appear within the target document, those members are _added_\n- If the target does contain the member, the value is _replaced_\n- _null_ values in the merge patch indicate that existing values in the target document are to be _removed_\n- Other values in the target document will remain _untouched_\n\nA request to modify John's job title could be:\n\n```http\nPATCH /contacts/1 HTTP/1.1\nHost: example.org\nContent-Type: application/merge-patch+json\n\n{\n  \"work\": {\n    \"title\": \"Senior Engineer\"\n  }\n}\n```\n\n## JSON-P: Java API for JSON Processing\n\n_JSON-P_ 1.0, defined in the JSR 353 and also known as _Java API for JSON Processing_ 1.0, brought official support for JSON processing in Java EE. JSON-P 1.1, defined in the JSR 374, introduced support for JSON Patch and JSON Merge Patch formats to Java EE.\n\nLet's have a quick look at the API to start getting familiar with it: \n\n| Type | Description |\n| ---- | ----------- |\n| [`Json`][javax.json.Json] | Factory class for creating JSON processing objects |\n| [`JsonPatch`][javax.json.JsonPatch] | Represents an implementation of JSON Patch |\n| [`JsonMergePatch`][javax.json.JsonMergePatch] | Represents an implementation of JSON Merge Patch |\n| [`JsonValue`][javax.json.JsonValue] | Represents an immutable JSON value that can be an [_object_][javax.json.JsonObject], an [_array_][javax.json.JsonArray], a [_number_][javax.json.JsonNumber], a [_string_][javax.json.JsonString], [_`true`_][javax.json.JsonValue.TRUE], [_`false`_][javax.json.JsonValue.FALSE] or [_`null`_][javax.json.JsonValue.NULL] |\n| [`JsonStructure`][javax.json.JsonStructure] | Super type for the two structured types in JSON: [_object_][javax.json.JsonObject] and [_array_][javax.json.JsonArray] |\n\nTo patch using JSON Patch, we would have the following: \n\n```java\n// Target JSON document to be patched\nJsonObject target = ...;\n\n// Create JSON Patch document\nJsonPatch jsonPatch = Json.createPatchBuilder()\n        .replace(\"/work/title\", \"Senior Engineer\")\n        .build();\n\n// Apply the patch to the target document\nJsonValue patched = jsonPatch.apply(target);\n```\n\nAnd to patch using JSON Merge Patch, we would have the following:\n\n```java\n// Target JSON document to be patched\nJsonObject target = ...;\n\n// Create JSON Merge Patch document\nJsonMergePatch mergePatch = Json.createMergePatch(Json.createObjectBuilder()\n        .add(\"work\", Json.createObjectBuilder()\n                .add(\"title\", \"Senior Engineer\"))\n        .build());\n\n// Apply the patch to the target document\nJsonValue patched = mergePatch.apply(target);\n```\n\nHaving said that, let me highlight that JSON-P is just an API, that is, a set of interfaces. If we want to work with it, we need an _implementation_ such as [Apache Johnzon][johnzon]: \n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.apache.johnzon\u003c/groupId\u003e\n    \u003cartifactId\u003ejohnzon-core\u003c/artifactId\u003e\n    \u003cversion\u003e${johnzon.version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Parsing the request payload\n\nTo parse a `PATCH` request payload, we must take the following into account:\n\n- For an incoming request with the `application/json-patch+json` content type, the payload must be converted to an instance of [`JsonPatch`][javax.json.JsonPatch].\n- For an incoming request with the `application/merge-patch+json` content type, the payload must be converted to an instance of [`JsonMergePatch`][javax.json.JsonMergePatch].\n\nSpring MVC, however, doesn't know how to create instances of [`JsonPatch`][javax.json.JsonPatch] and [`JsonMergePatch`][javax.json.JsonMergePatch]. So we need to provide a custom [`HttpMessageConverter\u003cT\u003e`][org.springframework.http.converter.HttpMessageConverter] for each type. Fortunately it's pretty straightforward.\n\nFor convenience, let's extend [`AbstractHttpMessageConverter\u003cT\u003e`][org.springframework.http.converter.AbstractHttpMessageConverter] and annotate the implementation with [`@Component`][org.springframework.stereotype.Component], so Spring can pick it up:\n\n```java\n@Component\npublic class JsonPatchHttpMessageConverter extends AbstractHttpMessageConverter\u003cJsonPatch\u003e {\n   ...\n}\n```\n\nThe constructor will invoke the parent's constructor indicating the supported media type for this converter:\n\n```java\npublic JsonPatchHttpMessageConverter() {\n    super(MediaType.valueOf(\"application/json-patch+json\"));\n}\n```\n\nWe indicate that our converter supports the [`JsonPatch`][javax.json.JsonPatch] class:\n\n```java\n@Override\nprotected boolean supports(Class\u003c?\u003e clazz) {\n    return JsonPatch.class.isAssignableFrom(clazz);\n}\n```\n\nThen we implement the method that will read the HTTP request payload and convert it to a [`JsonPatch`][javax.json.JsonPatch] instance:\n\n```java\n@Override\nprotected JsonPatch readInternal(Class\u003c? extends JsonPatch\u003e clazz, HttpInputMessage inputMessage)\n        throws HttpMessageNotReadableException {\n\n    try (JsonReader reader = Json.createReader(inputMessage.getBody())) {\n        return Json.createPatch(reader.readArray());\n    } catch (Exception e) {\n        throw new HttpMessageNotReadableException(e.getMessage(), inputMessage);\n    }\n}\n```\n\nIt's unlikely we'll need to write [`JsonPatch`][javax.json.JsonPatch] instances to the responses, but we could implement it as follows:\n\n```java\n@Override\nprotected void writeInternal(JsonPatch jsonPatch, HttpOutputMessage outputMessage)\n        throws HttpMessageNotWritableException {\n\n    try (JsonWriter writer = Json.createWriter(outputMessage.getBody())) {\n        writer.write(jsonPatch.toJsonArray());\n    } catch (Exception e) {\n        throw new HttpMessageNotWritableException(e.getMessage(), e);\n    }\n}\n```\n\nThe message converter for [`JsonMergePatch`][javax.json.JsonMergePatch] is pretty much the same as the converter described above (except for the types handled by the converter):\n\n```java\n@Component\npublic class JsonMergePatchHttpMessageConverter extends AbstractHttpMessageConverter\u003cJsonMergePatch\u003e {\n\n    public JsonMergePatchHttpMessageConverter() {\n        super(MediaType.valueOf(\"application/merge-patch+json\"));\n    }\n\n    @Override\n    protected boolean supports(Class\u003c?\u003e clazz) {\n        return JsonMergePatch.class.isAssignableFrom(clazz);\n    }\n\n    @Override\n    protected JsonMergePatch readInternal(Class\u003c? extends JsonMergePatch\u003e clazz, HttpInputMessage inputMessage)\n            throws HttpMessageNotReadableException {\n\n        try (JsonReader reader = Json.createReader(inputMessage.getBody())) {\n            return Json.createMergePatch(reader.readValue());\n        } catch (Exception e) {\n            throw new HttpMessageNotReadableException(e.getMessage(), inputMessage);\n        }\n    }\n\n    @Override\n    protected void writeInternal(JsonMergePatch jsonMergePatch, HttpOutputMessage outputMessage)\n            throws HttpMessageNotWritableException {\n\n        try (JsonWriter writer = Json.createWriter(outputMessage.getBody())) {\n            writer.write(jsonMergePatch.toJsonValue());\n        } catch (Exception e) {\n            throw new HttpMessageNotWritableException(e.getMessage(), e);\n        }\n    }\n}\n```\n\n## Creating the controller methods\n\nWith the HTTP message converters in place, we can receive [`JsonPatch`][javax.json.JsonPatch] and [`JsonMergePatch`][javax.json.JsonMergePatch] as method arguments in our controller methods, annotated with [`@RequestBody`][org.springframework.web.bind.annotation.RequestBody]:\n\n```java\n@PatchMapping(path = \"/{id}\", consumes = \"application/json-patch+json\")\npublic ResponseEntity\u003cVoid\u003e updateContact(@PathVariable Long id,\n                                          @RequestBody JsonPatch patchDocument) {\n    ...\n}\n```\n\n```java\n@PatchMapping(path = \"/{id}\", consumes = \"application/merge-patch+json\")\npublic ResponseEntity\u003cVoid\u003e updateContact(@PathVariable Long id,\n                                          @RequestBody JsonMergePatch mergePatchDocument) {\n    ...\n}\n```\n\n## Applying the patch\n\nIt is worth it to mention that both JSON Patch and JSON Merge Patch operate over JSON documents. \n\nSo, to apply the patch to a Java bean, we first need to convert the Java bean to a JSON-P type, such as [`JsonStructure`][javax.json.JsonStructure] or [`JsonValue`][javax.json.JsonValue]. Then we apply the patch to it and convert the patched document back to a Java bean:\n\n\u003c!-- Hack to center the image in GitHub --\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"misc/images/patch-conversions.png\" alt=\"Patch conversions\" width=\"100%\"/\u003e\n\u003c/p\u003e\n\nThese conversions could be handled by Jackson, which provides an [extension module][jackson-datatype-jsr353] to work with JSON-P types. With this extension module, we can read JSON as [`JsonValue`][javax.json.JsonValue]s and write [`JsonValue`][javax.json.JsonValue]s as JSON as part of normal Jackson processing, taking advantage of the powerful data-binding features that Jackson provides:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.fasterxml.jackson.datatype\u003c/groupId\u003e\n    \u003cartifactId\u003ejackson-datatype-jsr353\u003c/artifactId\u003e\n    \u003cversion\u003e${jackson.version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nWith module extension dependency on the classpath, we can configure the [`ObjectMapper`][com.fasterxml.jackson.databind.ObjectMapper] and expose it as a Spring [`@Bean`][org.springframework.context.annotation.Bean] (so it can be picked up by String and can be injected in other Spring beans):  \n\n```java\n@Bean\npublic ObjectMapper objectMapper() {\n    return new ObjectMapper()\n            .setDefaultPropertyInclusion(Include.NON_NULL)\n            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)\n            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)\n            .findAndRegisterModules();\n}\n```\n\nThe [`findAndRegisterModules()`][com.fasterxml.jackson.databind.ObjectMapper.findAndRegisterModules] method is important here: it tells Jackson to search and register the any modules found in the classpath, including the [`jackson-datatype-jsr353`][jackson-datatype-jsr353] extension module. Alternatively, we can register the module manually: \n\n```java\nmapper.registerModule(new JSR353Module());\n```\n\nOnce the [`ObjectMapper`][com.fasterxml.jackson.databind.ObjectMapper] is configured, we can inject it in our Spring beans and create a method to apply the JSON Patch to a Java bean:\n\n```java\npublic \u003cT\u003e T patch(JsonPatch patch, T targetBean, Class\u003cT\u003e beanClass) {\n    \n    // Convert the Java bean to a JSON document\n    JsonStructure target = mapper.convertValue(targetBean, JsonStructure.class);\n    \n    // Apply the JSON Patch to the JSON document\n    JsonValue patched = patch.apply(target);\n    \n    // Convert the JSON document to a Java bean and return it\n    return mapper.convertValue(patched, beanClass);\n}\n```\n\nAnd here's the method to patch using JSON Merge Patch:\n\n```java\npublic \u003cT\u003e T mergePatch(JsonMergePatch mergePatch, T targetBean, Class\u003cT\u003e beanClass) {\n    \n    // Convert the Java bean to a JSON document\n    JsonValue target = mapper.convertValue(targetBean, JsonValue.class);\n    \n    // Apply the JSON Merge Patch to the JSON document\n    JsonValue patched = mergePatch.apply(target);\n    \n    // Convert the JSON document to a Java bean and return it\n    return mapper.convertValue(patched, beanClass);\n}\n```\n\nWith this in place, the controller method implementation for JSON Patch could be like:\n\n```java\n@PatchMapping(path = \"/{id}\", consumes = \"application/json-patch+json\")\npublic ResponseEntity\u003cVoid\u003e updateContact(@PathVariable Long id,\n                                          @RequestBody JsonPatch patchDocument) {\n\n    // Find the model that will be patched\n    Contact contact = contactService.findContact(id).orElseThrow(ResourceNotFoundException::new);\n    \n    // Apply the patch\n    Contact contactPatched = patch(patchDocument, contact, Contact.class);\n    \n    // Persist the changes\n    contactService.updateContact(contactPatched);\n\n    // Return 204 to indicate the request has succeeded\n    return ResponseEntity.noContent().build();\n}\n```\n\nAnd the implementation is quite similar for JSON Merge Patch, except for the media type and for the types handled by the method:\n\n```java\n@PatchMapping(path = \"/{id}\", consumes = \"application/merge-patch+json\")\npublic ResponseEntity\u003cVoid\u003e updateContact(@PathVariable Long id,\n                                          @RequestBody JsonMergePatch mergePatchDocument) {\n\n    // Find the model that will be patched\n    Contact contact = contactService.findContact(id).orElseThrow(ResourceNotFoundException::new);\n    \n    // Apply the patch\n    Contact contactPatched = mergePatch(mergePatchDocument, contact, Contact.class);\n    \n    // Persist the changes\n    contactService.updateContact(contactPatched);\n\n    // Return 204 to indicate the request has succeeded\n    return ResponseEntity.noContent().build();\n}\n```\n\n## Validating the patch\n\nOnce the patch has been applied and before persisting the changes, we must ensure that the patch didn't lead the resource to an _invalid_ state. We could use [Bean Validation annotations][javax.validation.constraints] to define constraints and then ensure that the state of the model is valid. \n\n```java\npublic class Contact {\n\n    @NotBlank\n    private String name;\n    \n    ...\n}\n```\n\nTo perform the validation, we could inject [`Validator`][javax.validation.Validator] in our class and invoke the [`validate()`][javax.validation.Validator.validate] method. If any constraint has been violated, it will return a set of [`ConstraintViolation\u003cT\u003e`][javax.validation.ConstraintViolation] and then we can throw a [`ConstraintViolationException`][javax.validation.ConstraintViolationException]. So the method to apply the patch could be updated to handle the validation, as shown below:\n\n```java\npublic \u003cT\u003e T patch(JsonPatch patch, T targetBean, Class\u003cT\u003e beanClass) {\n    \n    // Convert the Java bean to a JSON document\n    JsonStructure target = mapper.convertValue(targetBean, JsonStructure.class);\n    \n    // Apply the JSON Patch to the JSON document\n    JsonValue patched = applyPatch(patch, target);\n\n    // Convert the JSON document to a Java bean\n    T beanPatched = mapper.convertValue(patched, beanClass);\n\n    // Validate the Java bean and throw an excetion if any constraint has been violated\n    Set\u003cConstraintViolation\u003cT\u003e\u003e violations = validator.validate(beanPatched);\n    if (!violations.isEmpty()) {\n        throw new ConstraintViolationException(violations);\n    }\n\n    // Return the bean that has been patched\n    return beanPatched;\n}\n```\n\nAlternatively, we could simply annotate the method with [`@Valid`][javax.validation.Valid] and Bean Validation will take care of performing the the validation on the returned value (the Spring bean may need to be annotated with [`@Validated`][org.springframework.validation.annotation.Validated] to trigger the validation):\n\n```java\n@Valid\npublic \u003cT\u003e T patch(JsonPatch patch, T targetBean, Class\u003cT\u003e beanClass) {\n    \n    // Convert the Java bean to a JSON document\n    JsonStructure target = mapper.convertValue(targetBean, JsonStructure.class);\n    \n    // Apply the JSON Patch to the JSON document\n    JsonValue patched = applyPatch(patch, target);\n\n    // Convert the JSON document to a Java bean and return it\n    return mapper.convertValue(patched, beanClass);\n}\n```\n\n## Bonus: Decoupling the domain model from the API model\n\nThe models that represent the _domain_ of our application and the models that represent the _data handled by our API_ are (or at least should be) _different concerns_ and should be _decoupled_ from each other. We don't want to break our API clients when we add, remove or rename a field from the application domain model.\u003csup name=\"a2\"\u003e[[2]](#f2)\u003c/sup\u003e \n\nWhile our service layer operates over the domain/persistence models, our API controllers should operate over a different set of models. As our domain/persistence models evolve to support new business requirements, for example, we may want to create new versions of the API models to support these changes. We also may want to deprecate the old versions of our API as new versions are released. And it's perfectly possible to achieve when the things are _decoupled_.\n\nTo minimize the boilerplate code of converting the domain model to the API model (and vice versa), we could rely on frameworks such as [MapStruct][mapstruct]. And we also could consider using [Lombok][lombok] to generate getters, setters, `equals()`, `hashcode()` and `toString()` methods for us.\n \nBy decoupling the API model from domain model, we also can ensure that we expose only the fields that can be modified. For example, we don't want to allow the client to modify the `id` field of our domain model. So our API model shouldn't contain the `id` field (and any attempt to modify it may cause an error or may be ignored).\n\nIn this example, the domain model class is called `Contact` and the model class that represents a resource is called `ContactResourceInput`. To convert between these two models with MapStruct, we could define a mapper interface and MapStruct will generate an implementation for it:\n\n```java\n@Mapper(componentModel = \"spring\")\npublic interface ContactMapper {\n\n    ContactResourceInput asContactResourceInput(Contact contact);\n\n    void update(ContactResourceInput contactResource, @MappingTarget Contact contact);\n    \n    ...\n}\n```  \n\nThe `ContactMapper` implementation will be exposed as a Spring [`@Component`][org.springframework.stereotype.Component], so it can be injected in other Spring beans. Let me highlight that _MapStruct doesn't use reflections_. Instead, it creates an actual implementation for the mapper interface and we can even check the code if we want to.\n\nOnce the `ContactMapper` is injected in our controller, we can use it to handle the model conversion. Here's what the controller method for handling `PATCH` requests with JSON Patch could be like:\n\n```java\n@PatchMapping(path = \"/{id}\", consumes = \"application/json-patch+json\")\npublic ResponseEntity\u003cVoid\u003e updateContact(@PathVariable Long id,\n                                          @RequestBody JsonPatch patchDocument) {\n\n    // Find the domain model that will be patched\n    Contact contact = contactService.findContact(id).orElseThrow(ResourceNotFoundException::new);\n    \n    // Map the domain model to an API resource model\n    ContactResourceInput contactResource = contactMapper.asContactResourceInput(contact);\n    \n    // Apply the patch to the API resource model\n    ContactResourceInput contactResourcePatched = patch(patchDocument, contactResource, ContactResourceInput.class);\n\n     // Update the domain model with the details from the API resource model\n    contactMapper.update(contactResourcePatched, contact);\n    \n    // Persist the changes\n    contactService.updateContact(contact);\n\n    // Return 204 to indicate the request has succeeded\n    return ResponseEntity.noContent().build();\n}\n```\n\nAnd, for comparision purposes, here's a controller method for handling `PUT` requests:\n\n```java\n@PutMapping(path = \"/{id}\", consumes = \"application/json\")\npublic ResponseEntity\u003cVoid\u003e updateContact(@PathVariable Long id,\n                                          @RequestBody @Valid ContactResourceInput contactResource) {\n\n    // Find the domain model that will be updated\n    Contact contact = contactService.findContact(id).orElseThrow(ResourceNotFoundException::new);\n    \n    // Update the domain model with the details from the API resource model\n    contactMapper.update(contactResource, contact);\n    \n    // Persist the changes\n    contactService.updateContact(contact);\n\n    // Return 204 to indicate the request has succeeded\n    return ResponseEntity.noContent().build();\n}\n```\n\n## References\n\n- [RFC 7231][rfc7231]: Semantics and content for the HTTP/1.1 protocol\n- [RFC 5789][rfc5789]: HTTP `PATCH` method\n- [RFC 6902][rfc6902]: JSON Patch\n- [RFC 7396][rfc7396]: JSON Merge Patch\n- [`javax.json`][javax.json]: Java API for JSON processing\n\n---\n\n##### Footnotes\n\n\u003cb id=\"f1\"\u003e[1]\u003c/b\u003e You may not want to support `PUT` for creating resources if you rely on the server to generate identifiers for your resources. See my [answer][so.56241060] on Stack Overflow for details on this. [↩](#a1)\n\n\u003cb id=\"f2\"\u003e[2]\u003c/b\u003e I also have described the benefits of this approach in this [answer][so.36175349] on Stack Overflow. [↩](#a2)\n\n  \n  [so.56241060]: https://stackoverflow.com/a/56241060/1426227\n  [so.36175349]: https://stackoverflow.com/a/36175349/1426227\n  \n  [org.springframework.http.converter.AbstractHttpMessageConverter]: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/converter/AbstractHttpMessageConverter.html\n  [org.springframework.stereotype.Component]: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/stereotype/Component.html\n  [org.springframework.http.converter.HttpMessageConverter]: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/converter/HttpMessageConverter.html\n  [org.springframework.web.bind.annotation.RequestBody]: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestBody.html\n  [org.springframework.validation.annotation.Validated]: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/validation/annotation/Validated.html\n  [org.springframework.context.annotation.Bean]: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html\n\n  [lombok]: https://projectlombok.org/  \n  [mapstruct]: http://mapstruct.org/\n  [johnzon]: https://johnzon.apache.org/\n  [jackson-datatype-jsr353]: https://github.com/FasterXML/jackson-datatype-jsr353\n  \n  [javax.json]: https://javaee.github.io/javaee-spec/javadocs/javax/json/package-summary.html\n  [javax.json.JsonPatch]: https://javaee.github.io/javaee-spec/javadocs/javax/json/JsonPatch.html\n  [javax.json.JsonMergePatch]: https://javaee.github.io/javaee-spec/javadocs/javax/json/JsonMergePatch.html\n  [javax.json.Json]: https://javaee.github.io/javaee-spec/javadocs/javax/json/Json.html\n  [javax.json.JsonValue]: https://javaee.github.io/javaee-spec/javadocs/javax/json/JsonValue.html\n  [javax.json.JsonValue.TRUE]: https://javaee.github.io/javaee-spec/javadocs/javax/json/JsonValue.html#TRUE\n  [javax.json.JsonValue.FALSE]: https://javaee.github.io/javaee-spec/javadocs/javax/json/JsonValue.html#FALSE\n  [javax.json.JsonValue.NULL]: https://javaee.github.io/javaee-spec/javadocs/javax/json/JsonValue.html#NULL\n  [javax.json.JsonStructure]: https://javaee.github.io/javaee-spec/javadocs/javax/json/JsonStructure.html\n  [javax.json.JsonObject]: https://javaee.github.io/javaee-spec/javadocs/javax/json/JsonObject.html\n  [javax.json.JsonArray]: https://javaee.github.io/javaee-spec/javadocs/javax/json/JsonArray.html\n  [javax.json.JsonNumber]: https://javaee.github.io/javaee-spec/javadocs/javax/json/JsonNumber.html\n  [javax.json.JsonString]: https://javaee.github.io/javaee-spec/javadocs/javax/json/JsonString.html\n\n  [javax.validation.constraints]: https://javaee.github.io/javaee-spec/javadocs/javax/validation/constraints/package-summary.html\n  [javax.validation.ConstraintViolation]: https://javaee.github.io/javaee-spec/javadocs/javax/validation/ConstraintViolation.html\n  [javax.validation.ConstraintViolationException]: https://javaee.github.io/javaee-spec/javadocs/javax/validation/ConstraintViolationException.html\n  [javax.validation.Validator]: https://javaee.github.io/javaee-spec/javadocs/javax/validation/Validator.html\n  [javax.validation.Validator.validate]: https://javaee.github.io/javaee-spec/javadocs/javax/validation/Validator.html#validate-T-java.lang.Class...-\n  [javax.validation.Valid]: https://javaee.github.io/javaee-spec/javadocs/javax/validation/Valid.html\n\n  [com.fasterxml.jackson.databind.ObjectMapper]: https://fasterxml.github.io/jackson-databind/javadoc/2.9/com/fasterxml/jackson/databind/ObjectMapper.html\n  [com.fasterxml.jackson.databind.ObjectMapper.findAndRegisterModules]: https://fasterxml.github.io/jackson-databind/javadoc/2.9/com/fasterxml/jackson/databind/ObjectMapper.html#findAndRegisterModules--\n\n  [rfc7231]: https://tools.ietf.org/html/rfc7231\n  [rfc7231.put]: https://tools.ietf.org/html/rfc7231#section-4.3.4\n\n  [rfc5789]: https://tools.ietf.org/html/rfc5789\n  [rfc5789.patch]: https://tools.ietf.org/html/rfc5789#section-2\n\n  [rfc6902]: https://tools.ietf.org/html/rfc6902\n  [rfc6902.add]: https://tools.ietf.org/html/rfc6902#section-4.1\n  [rfc6902.remove]: https://tools.ietf.org/html/rfc6902#section-4.2\n  [rfc6902.replace]: https://tools.ietf.org/html/rfc6902#section-4.3\n  [rfc6902.move]:https://tools.ietf.org/html/rfc6902#section-4.4\n  [rfc6902.copy]: https://tools.ietf.org/html/rfc6902#section-4.5\n  [rfc6902.test]: https://tools.ietf.org/html/rfc6902#section-4.6\n\n  [rfc7396]: https://tools.ietf.org/html/rfc7396\n  \n  [repo.postman]: https://github.com/cassiomolin/http-patch-spring/tree/master/misc/postman\n  [blog.post]:  https://cassiomolin.com/using-http-patch-in-spring/\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcassiomolin%2Fhttp-patch-spring","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcassiomolin%2Fhttp-patch-spring","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcassiomolin%2Fhttp-patch-spring/lists"}