Diagnosing HTTP PATCH Partial-Update Failures: JSON Patch vs JSON Merge Patch
HTTP PATCH endpoints fail or silently drop fields when JSON Patch and JSON Merge Patch are confused. Diagnose media-type, pointer, and concurrency causes, then apply targeted fixes.
26 Jul 2025, 22:07 UTC

Recognizable Condition
Partial updates to HTTP resources fail or mutate unexpectedly. The symptoms are specific: a 415 Unsupported Media Type when the client sends application/json to a JSON Patch endpoint, a 400 response mentioning operation, path, pointer, or JSON, a 409 or 412 when two clients edit the same record, fields that silently become null or disappear, array elements that are replaced wholesale instead of edited, and duplicate request keys that behave differently across servers. These failures usually mean the client and server disagree about the patch dialect, the media type, or the concurrency model.
Cause and Diagnostic Table
| Symptom | Likely Cause | Diagnostic Signal |
|---|---|---|
| 415 Unsupported Media Type | Content-Type is application/json or missing while the endpoint expects application/json-patch+json or application/merge-patch+json | Response body or Accept-Patch header lists the supported media types |
| 400 referencing pointer or path | Bad JSON Pointer; unescaped / or ~ in member names | Error message identifies pointer syntax or an array index |
| 400 referencing op or missing member | Unknown operation or missing required member such as value | Error identifies the operation array index or member name |
| 409 or 412 on concurrent edit | test operation failed or ETag/If-Match precondition is stale | Response includes the current ETag or full representation |
Field vanishes or becomes null | JSON Merge Patch treats null as delete; arrays are replaced wholesale | Before/after diff shows a missing key or whole-array substitution |
| Duplicate keys behave inconsistently | Parser-specific duplicate-key policy | Different servers or client libraries parse identical bytes differently |
Ordered Checks
- Capture the transaction. Record the method, URL,
Content-Type, exact body bytes, response status and body, and anyETag,If-Match, orIf-None-Matchheaders. Store the raw request so the failure can be reproduced. - Validate media type before parsing. Return 415 for unsupported types. Do not silently fall back to
application/jsonunless the endpoint explicitly documents that behavior. - Parse strictly with one duplicate-key policy. Validate patch operations, required members, pointer syntax, and array bounds before touching the document.
- Canonicalize parsed JSON for comparison. Never compare raw bytes for
testoperations; JSON object key order is not significant, but array order is. - Apply to a copy and diff. Mutate a copy, diff before/after, and record deletions explicitly.
- Re-read inside the transaction. Enforce version or
ETagpreconditions against the current stored state. - Return validators. Send the updated representation or a 204 plus the current
ETag.
Fixes Tied to Findings
Media-Type Mismatch (415)
Advertise only the PATCH media types the endpoint actually supports. If legacy clients send application/json, either map that type to an explicit schema or reject it with a 415 and a machine-readable list of accepted types. Assuming RFC 6902 and RFC 7396 semantics, a JSON Patch endpoint should require application/json-patch+json, and a merge-patch endpoint should require application/merge-patch+json.
Pointer and Operation Errors (400)
Fix pointers by escaping / as ~1 and ~ as ~0. Validate every token before application. Implement add, replace, remove, test, move, and copy; reject unknown operations with a machine-readable error that names the offending array index.
A representative JSON Patch request looks like this:
curl -X PATCH https://api.example.com/resources/123 \
-H "Content-Type: application/json-patch+json" \
-H "If-Match: \"resource-etag-placeholder\"" \
-d '[{"op":"replace","path":"/email","value":"[contact removed]"},{"op":"test","path":"/status","value":"active"}]'
Where to run: any HTTP client with network access to the API.
Permissions: the caller must hold update rights on the target resource.
Expected checks: 204 or 200 with an updated representation on success; 400 with pointer details on a malformed path; 412 if the If-Match precondition fails; 409 if the test operation fails.
Risk: omitting If-Match allows lost updates; sending the wrong Content-Type triggers 415 before the body is parsed.
Concurrency Conflicts (409/412)
Use test plus If-Match for optimistic concurrency. On mismatch, return 409 or 412 with the current ETag or the full representation so the client can rebase. A JSON Patch test operation is an assertion about document state, not authorization, validation, or transactional isolation.
Silent Field Loss and Array Replacement
If clients must set an explicit null, use JSON Patch replace or a sentinel value, because JSON Merge Patch cannot distinguish set to null from delete. For surgical array edits, use precise patch paths such as /items/0. For bulk replacement, document that merge patch replaces the entire array rather than merging element-by-element.
Practical Dialect Decision
Expose one primary partial-update dialect per endpoint. Choose JSON Patch when clients need add, remove, test, move, copy, and explicit nulls. Choose JSON Merge Patch for simple recursive object merges. Do not mix dialects on the same endpoint without a discriminator, and generate OpenAPI examples for both request bodies and 4xx responses.
Verification and Limitations
Practical check: reproduce with the stored request against a local instance. Assert the status code and that the response ETag changes only when the document actually changes. Unit-test pointer escaping with keys containing / and ~, plus whole-document ("") and empty-member ("/") pointers. Test merge patch with null, nested objects, and arrays; assert removed keys, recursive merge, and array replacement. Run a concurrency test where two clients PATCH from the same ETag; exactly one should succeed and the loser should receive 409 or 412.
Limitations: Content-Type does not prove the body is valid; always parse and validate before applying. Duplicate-key handling is parser-specific, so pin one behavior across client and server. Framework support for media types, RFC compatibility, and error shapes varies; verify behavior in your stack before promising it to clients.
Escalation Criteria
Escalate when the issue crosses a team boundary or risk threshold: authorization or mass-assignment concerns, multi-record corruption, non-deterministic results, inability to reproduce from the captured request, framework or library ambiguity, or a media-type change that would break existing clients. Route to the API owner plus security and data owners, and include the minimal captured request and before/after documents.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.