Jackson Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Introduction

Of the various HTTP methods available, the HTTP PATCH method plays a unique role. It allows us to apply partial updates to HTTP resources.

In this tutorial, we'll look at how to use the HTTP PATCH method along with the JSON Patch document format to apply partial updates to our RESTful resources.

2. The Use Case

Let's start by considering an example HTTP Customer resource represented by the JSON document:

{ 
    "id":"1",
    "telephone":"001-555-1234",
    "favorites":["Milk","Eggs"],
    "communicationPreferences": {"post":true, "email":true}
}

Let's assume that this customer's telephone number has changed and that the customer added a new item to their list of favorite products. This means that we need to update only the telephone and favorites fields of the Customer.

How would we do that?

The popular HTTP PUT method comes to mind first. However, because the PUT replaces a resource entirely, it's not a suitable method to apply partial updates elegantly. Moreover, the clients have to perform a GET before the updates are applied and saved.

This is where the HTTP PATCH method comes in handy.

Let's understand the HTTP PATCH method and the JSON Patch formats.

3. The HTTP PATCH Method and the JSON Patch Format

The HTTP PATCH method offers a nice way to apply partial updates to resources. As a result, clients need to send only the differences in their requests.

Let's look at a simple example of an HTTP PATCH request:

PATCH /customers/1234 HTTP/1.1
Host: www.example.com
Content-Type: application/example
If-Match: "e0023aa4e"
Content-Length: 100

[description of changes]

The HTTP PATCH request body describes how the target resource should be modified to produce a new version. Furthermore, the format used to represent the [description of changes] varies depending on the resource type. For JSON resource types, the format used to describe the changes is JSON Patch.

Simply put, the JSON Patch format uses a “series of operations” to describe how the target resource should be modified. A JSON Patch document is an array of JSON objects. Each object in the array represents exactly one JSON Patch operation.

Let's now look into the JSON Patch operations along with some examples.

4. JSON Patch Operations

A JSON Patch operation is represented by a single op object.

For example, here we're defining a JSON patch operation to update the customer's telephone number:

{
    "op":"replace",
    "path":"/telephone",
    "value":"001-555-5678"
}

Each operation must have one path member. Also, some operation objects must contain a from member as well. The value of the path and from members is a JSON Pointer. It refers to a location within the target document. This location can point to a specific key or an array element in the target object.

Let's now briefly look at the available JSON Patch operations.

4.1. The add Operation

We use the add operation to add a new member to an object. Also, we can use it to update an existing member and to insert a new value into the array at the specified index.

For example, let's add “Bread” to the customer's favorites list at index 0:

{
    "op":"add",
    "path":"/favorites/0",
    "value":"Bread"
}

The modified customer details after the add operation would be:

{
    "id":"1",
    "telephone":"001-555-1234",
    "favorites":["Bread","Milk","Eggs"],
    "communicationPreferences": {"post":true, "email":true}
}

4.2. The remove Operation

The remove operation removes a value at the target location. Besides, it can remove an element from an array at the specified index.

For instance, let's remove the communcationPreferences for our customer:

{
    "op":"remove",
    "path":"/communicationPreferences"
}

The modified customer details after the remove operation would be:

{
    "id":"1",
    "telephone":"001-555-1234",
    "favorites":["Bread","Milk","Eggs"],
    "communicationPreferences":null
}

4.3. The replace Operation

The replace operation updates the value at the target location with a new value.

As an example, let's update the telephone number for our customer:

{
    "op":"replace",
    "path":"/telephone",
    "value":"001-555-5678"
}

The modified customer details after the replace operation would be:

{ 
    "id":"1", 
    "telephone":"001-555-5678", 
    "favorites":["Bread","Milk","Eggs"], 
    "communicationPreferences":null
}

4.4. The move Operation

The move operation removes the value at the specified location and adds it to the target location.

For instance, let's move “Bread” from the top of the customer's favorites list to the bottom of the list:

{
    "op":"move",
    "from":"/favorites/0",
    "path":"/favorites/-"
}

The modified customer details after the move operation would be:

{ 
    "id":"1", 
    "telephone":"001-555-5678", 
    "favorites":["Milk","Eggs","Bread"], 
    "communicationPreferences":null
}

The /favorites/0 and /favorites/- in the above example are JSON pointers to the start and end indices of the favorites array.

4.5. The copy Operation

The copy operation copies the value at the specified location to the target location.

For example, let's duplicate “Milk” in the favorites list:

{
    "op":"copy",
    "from":"/favorites/0",
    "path":"/favorites/-"
}

The modified customer details after the copy operation would be:

{ 
    "id":"1", 
    "telephone":"001-555-5678", 
    "favorites":["Milk","Eggs","Bread","Milk"], 
    "communicationPreferences":null
}

4.6. The test Operation

The test operation tests that the value at the “path” is equal to the “value”. Because the PATCH operation is atomic, the PATCH should be discarded if any of its operations fail. The test operation can be used to validate that the preconditions and post-conditions have been met.

For instance, let's test that the update to the customer's telephone field has been successful:

{
    "op":"test", 
    "path":"/telephone",
    "value":"001-555-5678"
}

Let's now see how we can apply the above concepts to our example.

5. HTTP PATCH Request Using the JSON Patch Format

We'll revisit our Customer use case.

Here is the HTTP PATCH request to perform a partial update to the customer's telephone and favorites list using the JSON Patch format:

curl -i -X PATCH http://localhost:8080/customers/1 -H "Content-Type: application/json-patch+json" -d '[
    {"op":"replace","path":"/telephone","value":"+1-555-56"},
    {"op":"add","path":"/favorites/0","value":"Bread"}
]'

Most importantly, the Content-Type for JSON Patch requests is application/json-patch+json. Also, the request body is an array of JSON Patch operation objects:

[
    {"op":"replace","path":"/telephone","value":"+1-555-56"},
    {"op":"add","path":"/favorites/0","value":"Bread"}
]

How would we process such a request on the server-side?

One way is to write a custom framework that evaluates the operations sequentially and applies them to the target resource as an atomic unit.  Clearly, this approach sounds complicated. Also, it can lead to a non-standardized way of consuming patch documents.

Fortunately, we do not have to hand-craft the processing of JSON Patch requests.

The Java API for JSON Processing 1.0, or JSON-P 1.0, defined originally in JSR 353, introduced support for the JSON Patch in JSR 374. The JSON-P API provides the JsonPatch type to represent the JSON Patch implementation.

However, JSON-P is only an API. To work with the JSON-P API, we need to use a library that implements it. We'll use one such library called json-patch for the examples in this article.

Let's now look at how we can build a REST service that consumes HTTP PATCH requests using the JSON Patch format described above.

6. Implementing JSON Patch in a Spring Boot Application

6.1. Dependencies

The latest version of json-patch can be found from the Maven Central repository.

To begin with, let's add the dependencies to the pom.xml:

<dependency>
    <groupId>com.github.java-json-tools</groupId>
    <artifactId>json-patch</artifactId>
    <version>1.12</version>
</dependency>

Now, let's define a schema class to represent the Customer JSON document :

public class Customer {
    private String id;
    private String telephone;
    private List<String> favorites;
    private Map<String, Boolean> communicationPreferences;

    // standard getters and setters
}

Next, we'll look at our controller method.

6.2. The REST Controller Method

Then, we can implement HTTP PATCH for our customer use case:

@PatchMapping(path = "/{id}", consumes = "application/json-patch+json")
public ResponseEntity<Customer> updateCustomer(@PathVariable String id, @RequestBody JsonPatch patch) {
    try {
        Customer customer = customerService.findCustomer(id).orElseThrow(CustomerNotFoundException::new);
        Customer customerPatched = applyPatchToCustomer(patch, customer);
        customerService.updateCustomer(customerPatched);
        return ResponseEntity.ok(customerPatched);
    } catch (JsonPatchException | JsonProcessingException e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    } catch (CustomerNotFoundException e) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
    }
}

Let's now understand what is going on in this method:

  • To begin with, we use the @PatchMapping annotation to mark the method as a PATCH handler method
  • When a patch request with the application/json-patch+json “Content-Type” arrives, Spring Boot uses the default MappingJackson2HttpMessageConverter to convert the request payload to a JsonPatch instance. As a result, our controller method will receive the request body as a JsonPatch instance

Within the method:

  1. First, we call the customerService.findCustomer(id) method to find the customer record
  2. Subsequently, if the customer record is found, we invoke the applyPatchToCustomer(patch, customer) method. This applies the JsonPatch to the customer (more on this later)
  3. We then invoke the customerService.updateCustomer(customerPatched) to save the customer record
  4. Finally, we return a 200 OK response to the client with the patched Customer details in the response

Most importantly, the real magic happens in the applyPatchToCustomer(patch, customer) method:

private Customer applyPatchToCustomer(
  JsonPatch patch, Customer targetCustomer) throws JsonPatchException, JsonProcessingException {
    JsonNode patched = patch.apply(objectMapper.convertValue(targetCustomer, JsonNode.class));
    return objectMapper.treeToValue(patched, Customer.class);
}
  1. To begin with, we have our JsonPatch instance that holds the list of operations to be applied to the target Customer
  2. We then convert the target Customer into an instance of com.fasterxml.jackson.databind.JsonNode and pass it to the JsonPatch.apply method to apply the patch. Behind the scenes, the JsonPatch.apply deals with applying the operations to the target. The result of the patch is also a com.fasterxml.jackson.databind.JsonNode instance
  3. We then call the objectMapper.treeToValue method, which binds the data in the patched com.fasterxml.jackson.databind.JsonNode to the Customer type. This is our patched Customer instance
  4. Finally, we return the patched Customer instance

Let's now run some tests against our API.

6.3. Testing

To begin with, let's create a customer using a POST request to our API:

curl -i -X POST http://localhost:8080/customers -H "Content-Type: application/json" 
  -d '{"telephone":"+1-555-12","favorites":["Milk","Eggs"],"communicationPreferences":{"post":true,"email":true}}'

We receive a 201 Created response:

HTTP/1.1 201
Location: http://localhost:8080/customers/1

The Location response header is set to the location of the new resource. It indicates that the id of the new Customer is 1.

Next, let's request a partial update to this customer using a PATCH request:

curl -i -X PATCH http://localhost:8080/customers/1 -H "Content-Type: application/json-patch+json" -d '[
    {"op":"replace","path":"/telephone","value":"+1-555-56"}, 
    {"op":"add","path":"/favorites/0","value": "Bread"}
]'

We receive a 200 OK response with the patched customer details:

HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 14 Feb 2020 21:23:14 GMT

{"id":"1","telephone":"+1-555-56","favorites":["Bread","Milk","Eggs"],"communicationPreferences":{"post":true,"email":true}}

7. Conclusion

In this article, we looked at how to implement JSON Patch in Spring REST APIs.

To begin with, we looked at the HTTP PATCH method and its ability to perform partial updates.

We then looked into what is JSON Patch and understood the various JSON Patch operations.

Finally, we discussed how to handle an HTTP PATCH request in a Spring Boot application using the json-patch library.

As always, the source code for the examples used in this article is available over on GitHub.

Jackson bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
4 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Bjørn Vårdal
Bjørn Vårdal
9 months ago

Thanks for the article! This is a good way around the “POJO null field” problem where you can’t tell the difference between “set to null” and “not specified”. However, I noticed that this doesn’t match the specifications of JSON:API (https://jsonapi.org/format/#crud-updating). It feels like the approach in this article is a Spring/Java-ism bleeding out into the REST API. It definitely makes life easier for the Java developer, so my question is whether this is a good solution for the API user. Do you have any more insights to share regarding that? — Before submitting this, I found this article that appears… Read more »

Loredana Crusoveanu
2 months ago
Reply to  Bjørn Vårdal

Hi Bjørn,

Thanks for the feedback and the additional link. Indeed, this follows the JSON-P:https://javaee.github.io/jsonp/ specification, and not the JSON:API one.

Either format can be a good option for the API client. The goal is to standardize this for PATCH requests.

Alex I.
Alex I.
8 months ago

You say “Moreover, the clients have to perform a GET before the updates are applied and saved.” but if you fetch the resource yourself using the service layer so this patch method has the same disadvantage as The PUT method, you have to fetch the whole resource, it is not atomic and it can lead to inconsistenta data whem multiple client do updates concurently. Any way to apply the patch as an atomic operation?

Loredana Crusoveanu
2 months ago
Reply to  Alex I.

Hi Alex,

With PATCH, when we fetch the resource at the service layer, we can keep the update transactional so as to achieve atomicity.

Comments are closed on this article!