REST API Partial Updates with PATCH

Users rarely change a whole record at once. A person edits a phone number, toggles a setting, or fixes a typo. Sending the entire resource for such a tiny edit wastes bandwidth and risks overwriting other fields. PATCH solves this problem by sending only the changes.

The Form Editing Picture

Imagine a printed form with ten fields. PUT asks you to rewrite the whole form with a fresh copy. PATCH lets you cross out one field and write the new value beside it. The rest of the form stays untouched.

PUT and PATCH Side by Side

Stored user:  { "name": "Ravi", "email": "ravi@mail.com", "city": "Pune" }

PUT /users/9    body: { "name": "Ravi", "email": "ravi@mail.com", "city": "Delhi" }
                → client must send every field

PATCH /users/9  body: { "city": "Delhi" }
                → client sends only the change

A PUT request that leaves out a field may erase it. A PATCH request leaves unmentioned fields alone.

Two Patch Formats

JSON Merge Patch

JSON Merge Patch uses the media type application/merge-patch+json. The body looks like a smaller version of the resource. The server merges it into the stored data. A null value removes a field.

PATCH /users/9
Content-Type: application/merge-patch+json

{ "city": "Delhi", "nickname": null }

This format reads easily and suits most projects. It cannot change single items inside an array, because it replaces the whole array.

JSON Patch

JSON Patch uses the media type application/json-patch+json. The body holds a list of operations, each with an op, a path, and sometimes a value.

PATCH /users/9
Content-Type: application/json-patch+json

[
  { "op": "replace", "path": "/city", "value": "Delhi" },
  { "op": "add", "path": "/tags/-", "value": "premium" },
  { "op": "remove", "path": "/nickname" }
]

Operations include add, remove, replace, move, copy, and test. This format offers precise control over arrays and nested objects at the cost of extra complexity.

How the Server Handles a Patch

Request arrives
     │
     ▼
Load current resource ──→ Apply changes ──→ Validate result
                                                  │
                          ┌───────────────────────┴───────────┐
                          ▼                                   ▼
                    Valid: save                       Invalid: reject
                    return 200 or 204                 return 422 or 400

Validation runs on the final merged resource, not on the patch alone. A patch that sets an end date before the stored start date must fail even though the patch looks fine by itself.

Status Codes

  • 200 OK: the update worked, and the body shows the new resource.
  • 204 No Content: the update worked, and the response has no body.
  • 400 Bad Request: the patch document has a syntax problem.
  • 404 Not Found: the target resource does not exist.
  • 415 Unsupported Media Type: the server does not accept the patch format.
  • 422 Unprocessable Content: the patch is valid, and the resulting data breaks a rule.

Protecting Against Lost Updates

Two users may edit the same record at the same moment. Without protection, the last save wins and silently erases the other change. An ETag header solves this. The server sends a version tag with each read. The client returns it in an If-Match header with the patch. The server rejects the request with 412 Precondition Failed when the stored version has moved on.

GET /users/9          → ETag: "v5"
PATCH /users/9        If-Match: "v5"   → 200 OK, new ETag "v6"
PATCH /users/9        If-Match: "v5"   → 412 Precondition Failed

Idempotency of PATCH

A patch that sets city to Delhi gives the same result on every repeat. A patch that adds 1 to a counter changes the value on every repeat. Design patches as "set to this value" instructions whenever possible.

Common Mistakes

Using PATCH Like PUT

Requiring all fields in a PATCH body defeats the purpose. Accept any valid subset.

Allowing Protected Fields

A client should not patch fields such as id, createdAt, or role. Ignore or reject changes to read-only fields.