HTTP PATCH Method: Partial Updates in REST APIs

Learn how to use PATCH for partial resource updates, upsert patterns, idempotency, and JSON Patch format.

Last Updated:

What is the HTTP PATCH Method?

PATCH
Not Safe Conditionally Idempotent

The HTTP PATCH method applies partial modifications to a resource. Unlike PUT, which replaces an entire resource, PATCH lets you send only the fields that need to change. The server merges or applies those changes to the existing resource state.

PATCH was introduced in 2010 by RFC 5789, which noted a gap in HTTP: there was no standard method for partial updates. PUT was the closest, but it required sending the full resource representation on every update — wasteful for large resources and error-prone when concurrent clients might overwrite each other's changes.

Key Properties of PATCH

  • Not safe: PATCH modifies server state, so it is not a safe method.
  • Conditionally idempotent: PATCH is not inherently idempotent, but can be designed to be — depending on how the patch body is interpreted by the server.
  • Requires a request body: The body describes the modifications to apply.
  • Response codes: 200 OK (with updated resource) or 204 No Content (success, no body).

When PATCH is the right choice: use it when you need to update one or a few fields of a large resource, when bandwidth matters, or when you want to avoid accidentally overwriting fields you did not intend to change.

PATCH vs PUT: Key Differences

The most common source of confusion in REST API design is when to use PATCH versus PUT. They are both used for updates, but they have fundamentally different semantics.

Aspect PUT PATCH
Scope Replace the entire resource Modify specific fields only
Required fields All fields must be sent Only changed fields are sent
Idempotent Always idempotent Design-dependent
Bandwidth Higher (full resource each time) Lower (delta only)
Semantic Replace / overwrite Modify / apply changes
Missing fields Field is set to null or default Field remains unchanged

Code Comparison

Consider a user resource with fields: name, email, role, status. You want to update only the email address.

With PUT (full replacement — requires all fields):

PUT /api/v1/users/123

Request Body (must include ALL fields):

{
  "name": "John Doe",
  "email": "new@example.com",
  "role": "admin",
  "status": "active"
}

With PATCH (partial update — only the changed field):

PATCH /api/v1/users/123

Request Body (only the changed field):

{
  "email": "new@example.com"
}

Response (200 OK — full updated resource):

{
  "id": 123,
  "name": "John Doe",
  "email": "new@example.com",
  "role": "admin",
  "status": "active",
  "updated_at": "2026-03-24T10:00:00Z"
}

PATCH Request Format

RFC 5789 deliberately leaves the PATCH body format open — it does not mandate a specific representation. In practice, three formats have emerged as the most common.

1. Simple JSON Partial Object (Most Common)

Send a JSON object containing only the fields to update. The server applies these values to the existing resource, leaving all other fields untouched. This is by far the most widely used approach.

Request:

PATCH /api/v1/users/123 HTTP/1.1
Content-Type: application/json

{
  "email": "updated@example.com",
  "status": "inactive"
}

2. JSON Patch (RFC 6902)

JSON Patch (RFC 6902) defines a JSON document format for describing a sequence of operations to apply to a target JSON document. Each operation is an object with an op, a path, and optionally a value. Operations include: add, remove, replace, move, copy, test.

Request:

PATCH /api/v1/users/123 HTTP/1.1
Content-Type: application/json-patch+json

[
  { "op": "replace", "path": "/email", "value": "updated@example.com" },
  { "op": "replace", "path": "/status", "value": "inactive" },
  { "op": "add", "path": "/tags/-", "value": "verified" },
  { "op": "remove", "path": "/legacy_id" }
]

JSON Patch is powerful but verbose. It is best suited for APIs that need to describe complex document mutations, especially when clients need to atomically apply multiple operations.

3. JSON Merge Patch (RFC 7396)

JSON Merge Patch (RFC 7396) uses a simpler model: fields present in the patch body are updated, fields absent are unchanged, and fields explicitly set to null are deleted from the target resource.

Request:

PATCH /api/v1/users/123 HTTP/1.1
Content-Type: application/merge-patch+json

{
  "email": "updated@example.com",
  "legacy_id": null
}

Effect: email is updated, legacy_id field is removed, all other fields unchanged.

Format Content-Type Complexity Best For
Simple JSON Partial application/json Low Most REST APIs
JSON Patch (RFC 6902) application/json-patch+json High Complex document mutations
JSON Merge Patch (RFC 7396) application/merge-patch+json Medium When null = delete semantics needed

Making PATCH Idempotent

PATCH is not idempotent by default — sending the same request twice may produce different results depending on what the patch does. Understanding this distinction is critical for building reliable APIs.

Non-Idempotent PATCH (Counter Increment)

If your PATCH applies a relative change — like incrementing a counter — then calling it multiple times produces different outcomes:

Request (not idempotent — each call increments the counter):

PATCH /api/v1/posts/42 HTTP/1.1
Content-Type: application/json

{
  "views": "+1"
}

Calling this 3 times adds 3 to the view count — not idempotent.

Idempotent PATCH (Absolute Value)

If your PATCH sets an absolute value — like changing a status string — then calling it multiple times always produces the same state:

Request (idempotent — sets status to a specific value):

PATCH /api/v1/orders/99 HTTP/1.1
Content-Type: application/json

{
  "status": "shipped"
}

Calling this 3 times always results in status = "shipped" — idempotent.

Using If-Match for Optimistic Locking

For concurrent update safety, use the If-Match header with an ETag. The server only applies the patch if the current ETag matches. This prevents lost-update problems when multiple clients patch the same resource simultaneously.

Request with conditional header:

PATCH /api/v1/users/123 HTTP/1.1
Content-Type: application/json
If-Match: "etag-abc123"

{
  "email": "new@example.com"
}

If ETag matches: 200 OK with updated resource.
If ETag does not match (stale): 412 Precondition Failed.

With If-Match, the PATCH becomes effectively idempotent from the perspective of the caller's intent: it will only succeed once for a given version of the resource.

Upsert Pattern with PATCH

What is an Upsert?

An upsert (update + insert) is an operation that creates a resource if it does not exist, or updates it if it does. Upserts are useful when clients are authoritative about a resource's identity and want to set its state regardless of whether it already exists.

PUT Upsert (Most Common)

PUT is naturally suited to upserts because it replaces or creates a resource at a specific URL. If the resource does not exist, the server creates it and returns 201 Created. If it exists, the server replaces it and returns 200 OK.

PUT /api/v1/settings/user-123-preferences

Request Body (full resource state):

{
  "theme": "dark",
  "language": "en",
  "notifications": true
}

201 Created (if new) or 200 OK (if replaced)

Conditional Headers for Upsert Control

Use conditional headers to control upsert behavior precisely:

  • If-None-Match: * — only create; fail with 412 if resource already exists
  • If-Match: "etag" — only update; fail with 412 if resource does not match ETag
  • No conditional header — upsert (create or replace)

Create-only request (fails if already exists):

PUT /api/v1/settings/user-123-preferences HTTP/1.1
Content-Type: application/json
If-None-Match: *

{
  "theme": "dark",
  "language": "en",
  "notifications": true
}

201 Created (if new) or 412 Precondition Failed (if already exists)

PATCH Upsert

PATCH can also implement upsert behavior. This is less common because PATCH implies a partial update — and partial updates against a non-existent resource are ambiguous. However, some APIs support it for convenience when the client wants to initialize a resource with partial state.

PATCH /api/v1/settings/user-123-preferences

Request Body:

{
  "theme": "dark"
}

Server behavior: if resource exists, update theme; if not, create with theme = "dark" and defaults for other fields.

PUT Upsert vs PATCH Upsert Comparison

Aspect PUT Upsert PATCH Upsert
Fields required All fields (full state) Only specified fields
Default values for missing fields Must be specified or nulled Server applies defaults
Idempotent Yes Design-dependent
Semantic clarity High — well-defined by HTTP spec Lower — non-standard behavior
Recommendation Preferred for upserts Only if partial init is needed

Real-World PATCH Examples

Update User Email

A user changes their email address in their account settings. Only the email field changes — there is no reason to send the entire user object.

PATCH /api/v1/users/123

Request Body:

{
  "email": "john.new@example.com"
}

Response (200 OK):

{
  "id": 123,
  "name": "John Doe",
  "email": "john.new@example.com",
  "role": "admin",
  "status": "active",
  "updated_at": "2026-03-24T10:30:00Z"
}

Update Order Status

An order management system transitions an order through lifecycle states. PATCH is ideal here — only the status and tracking number change.

PATCH /api/v1/orders/456

Request Body:

{
  "status": "shipped",
  "tracking_number": "1Z999AA10123456784",
  "shipped_at": "2026-03-24T08:00:00Z"
}

Response (200 OK):

{
  "id": 456,
  "status": "shipped",
  "tracking_number": "1Z999AA10123456784",
  "shipped_at": "2026-03-24T08:00:00Z",
  "updated_at": "2026-03-24T10:31:00Z"
}

Toggle Feature Flags

Feature flag systems often use PATCH to enable or disable individual flags without replacing the entire flags configuration.

PATCH /api/v1/apps/myapp/features

Request Body:

{
  "dark_mode": true,
  "beta_dashboard": false
}

Response (200 OK):

{
  "dark_mode": true,
  "beta_dashboard": false,
  "new_checkout": true,
  "analytics_v2": false
}

Common PATCH Mistakes

Mistake 1: Sending the Full Object in a PATCH Body

PATCH should contain only the fields being changed. Sending the entire resource defeats the purpose and can introduce subtle bugs — for example, overwriting a field that another concurrent request just updated. If you want to replace the entire resource, use PUT instead.

Wrong — full object in PATCH:

PATCH /api/v1/users/123
{ "name": "John", "email": "john@example.com", "role": "admin", "status": "active" }

Correct — only the changed field:

PATCH /api/v1/users/123
{ "email": "john@example.com" }

Mistake 2: Not Handling Partial Validation Correctly

PATCH validation must be contextual. Required field validators designed for POST or PUT will incorrectly reject PATCH requests that omit non-changed required fields. Your validation logic must distinguish between "field absent from PATCH body" (ignore) and "field explicitly sent as null" (potentially invalid).

Mistake 3: Wrong Content-Type Header

Always specify the correct Content-Type for your PATCH body format. Sending a JSON Patch document with Content-Type: application/json instead of application/json-patch+json can cause the server to misinterpret the body.

PATCH /api/v1/users/123 HTTP/1.1
Content-Type: application/json          <-- simple partial object
Content-Type: application/json-patch+json  <-- JSON Patch (RFC 6902)
Content-Type: application/merge-patch+json <-- JSON Merge Patch (RFC 7396)

Mistake 4: Unintentional Non-Idempotency

Be deliberate about whether your PATCH endpoint is idempotent. If clients may retry failed PATCH requests (common in unreliable network conditions), non-idempotent PATCH bodies — like increment operations — can corrupt data. Use absolute values in PATCH bodies when idempotency matters, or use If-Match headers to prevent duplicate application.

Frequently Asked Questions

Is PATCH idempotent?

PATCH is not inherently idempotent, but it can be designed to be. If a PATCH request sets an absolute value — for example, {"status": "published"} — then applying it multiple times always produces the same result (idempotent). If it applies a relative change — for example, {"views": "+1"} — then each call produces a different result (not idempotent). For idempotent PATCH, always use absolute values and consider the If-Match header for concurrent update safety.

What is the difference between PATCH and PUT?

PUT replaces the entire resource — you must send all fields, and any field omitted is set to null or its default. PATCH applies partial modifications — you send only the fields that need to change, and all other fields remain untouched. Use PUT when you want to fully replace a resource's state. Use PATCH when you want to update specific fields without affecting the rest.

What format should a PATCH request body be?

There are three common formats: (1) Simple JSON partial object — the most common approach, send only the fields to update with Content-Type: application/json. (2) JSON Patch (RFC 6902) — an array of operation objects describing changes, with Content-Type: application/json-patch+json, more powerful but verbose. (3) JSON Merge Patch (RFC 7396) — similar to simple partial but where null explicitly removes a field, with Content-Type: application/merge-patch+json. Most REST APIs use the simple JSON partial object approach.

Does PATCH create a resource if it does not exist?

By default, a PATCH request to a non-existent resource should return 404 Not Found. However, PATCH can support upsert behavior — creating a resource if it does not exist — when the server is designed to handle this case. Use the If-None-Match: * header to signal "create only if it does not exist." The server returns 201 Created for a new resource or applies the patch to the existing one. This is non-standard behavior, so document it clearly in your API.