HTTP PATCH Method: Partial Updates in REST APIs
Learn how to use PATCH for partial resource updates, upsert patterns, idempotency, and JSON Patch format.
What is the HTTP PATCH Method?
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):
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):
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.
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 existsIf-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.
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.
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.
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.
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.
Related Topics
HTTP Methods Overview
Compare all HTTP methods: GET, POST, PUT, PATCH, DELETE, and OPTIONS.
HTTP OPTIONS & CORS
How OPTIONS works for CORS preflight requests and API discovery.
HTTP Status Codes
Which status codes to return from PATCH endpoints: 200, 204, 404, 412.
REST API Best Practices
URL design, versioning, and API design patterns for production APIs.