mscharhag, Programming and Stuff;

A blog about programming and software development topics, mostly focused on Java technologies including Java EE, Spring and Grails.

Tuesday, 20 October, 2020

REST: Updating resources

When building RESTful APIs over HTTP the PUT method is typically used for updating, while POST is used for creating resources. However, create and update operations do not perfectly align with the HTTP verbs PUT and POST. In certain situations PUT can also be used for resource creation. See my post about the differences between POST, PUT and PATCH for more details.

Within the next sections we will look at updating resources with PUT.

Note that this post does not cover partial updates (e.g. updating only a single field) which can be done with HTTP PATCH. This topic will be covered in a separate future blog post.

Updating resource with HTTP PUT

HTTP PUT replaces the resource at the request URI with the given values. This means the request body has to contain all available values, even if we only want to update a single field.

Assume we want to update the product with ID 345. An example request might look like this:

PUT /products/345
Content-Type: application/json

{
    "name": "Cool Gadget",
    "description": "Looks cool",
    "price": "24.99 USD"
}

Responses to HTTP PUT update operations

You can find various discussions about the question if an update via HTTP PUT should return the updated response.

There is no single true here. If you think it is useful to return the updated resource in your situation: do it. Just make sure to be consistent for all update operations in your API.

The server responds to HTTP PUT requests usually with one of the following HTTP status codes:

  • HTTP 200 (Ok): The request has been processes successfully and the response contains the updated resource.
  • HTTP 204 (No content): The request has been processed successfully. The updated resource is not part of the response.
  • HTTP 400 (Bad request): The operation failed due to invalid request parameters (e.g. missing or invalid values in the request body).

Note that responses to HTTP PUT are not cacheable (See the last paragraph of RFC 7231 4.3.4).

Replacing resources in real-life

As mentioned earlier HTTP PUT replaces the resource at a given URI. In real-life this can lead to various discussions because resources are often not really replaced.

Assume we send an GET request to the previously used product resource. The response payload might look like this:

GET /products/345
{
    "id": 345,
    "name": "Cool Gadget",
    "description": "Looks cool",
    "price": "24.99 USD",
    "lastUpdated": "2020-10-17T09:31:17",
    "creationDate": "2029-12-21T07:14:31",
    "_links": [
        { "rel": "self", "href": "/products/345"},
        ..
    ]
}

Besides name, description and price we get the product ID, creation and update dates and a hypermedia _links element.

id and creationDate are set by the server when the resource is created. lastUpdated is set whenever the resource is updated. Resource links are built by the server based on the current resource state.

In practice there is no reason why an update request needs to contain those fields. They are either ignored by the server or can only lead to HTTP 400 responses if the client sends unexpected values.

One point can be made here about lastUpdated. It would be possible to use this field to detect concurrent modification on the server. In this case, clients send the lastUpdated field they retrieved via a previous GET request back to the server. On an update request the server can now compare the lastUpdated value from the request with the one stored on the server. If the server state is newer, the server responds with HTTP 409 (Conflict) to notify the client that the resource has been changed since the last GET request.

However, the same can be accomplished using the HTTP ETag header in a more standardized way.

Now it can be discussed if we really replace the resource if we do not send certain fields with the PUT request.

I recommend being pragmatic and only require the fields that can be modified by the client. Other fields can be skipped. However, the server should not deny the request if other fields are sent. Those fields should just be ignored. This gives the client the option to retrieve the resource via a GET request, modify it and send it back to the server.

HTTP PUT and idempotency

The PUT method is idempotent. This means that multiple identical PUT requests must result in the same outcome. Typically no extra measures are required to achieve this as update behavior is usually idempotent.

However, if we look at the previous example GET request, there is again something that can be discussed:

Does the lastUpdated field break idempotency for update requests?

There are (at least) two valid ways to implement a lastUpdated field on the server:

  • lastUpdated changes whenever the resource state changes. In this case we have no idempotency-issue. If multiple identical PUT requests are sent, only the first one changes the lastUpdated field.
  • lastUpdated changes with every update request even if the resource state does not change. Here lastUpdated tells us how up-to-date the resource state is (and not when it changed the last time). Sending multiple identical update requests results in a changing lastUpdated field for every request.

I would argue that even the second implementation is not a real problem for idempotency.    

The HTTP RFC says:

Like the definition of safe, the idempotent property only applies to what has been requested by the user; a server is free to log each request separately, retain a revision control history, or implement other non-idempotent side effects for each idempotent request.

A changing lastUpdated field can be seen as a non-idempotent side effect. It has not been actively requested by the user and is completely managed by the server.

Leave a reply