PUT vs PATCH: Guide to HTTP Update Methods

PUT vs PATCH?

A ground-up walkthrough of the two HTTP update methods everyone confuses — how they work internally, why idempotency matters, how JSON Patch and JSON Merge Patch differ, and how Netflix, Stripe, GitHub and Kubernetes actually use them in production.

PUT vs PATCH
01
Introduction & History

What is PUT vs PATCH, really?

Two HTTP verbs, one job, endless confusion. Before we dive into specs and RFCs, let’s start with the picture that clears it up in ten seconds.

Imagine you keep a notebook with a friend’s contact card in it: name, phone number, address, and birthday. One day your friend moves to a new house. You have two ways to fix the notebook. You can cross out the entire card and rewrite it from scratch with the new address included, or you can erase just the one line that says “address” and write the new one in.

The first approach — rewriting the whole card — is exactly what the HTTP method PUT does. The second approach — touching only the part that changed — is what PATCH does. That is the entire idea in a nutshell. Everything else in this guide is just filling in the details of why that distinction matters so much to the people building real systems.

Everyday Analogy

PUT is like handing someone a brand-new, fully filled-out form and saying “replace what you had with this.” PATCH is like sticking a Post-it on their existing form that says “change just this one field to this new value.”

HTTP (HyperText Transfer Protocol) is the language browsers, mobile apps, and servers use to talk to each other. When it was first standardised in the 1990s (RFC 1945, then RFC 2068 and RFC 2616), the protocol defined a small set of “methods” — verbs like GET, POST, PUT, and DELETE — that describe the intent of a request. PUT was there from nearly the beginning, defined as early as HTTP/1.0-era drafts with the clear meaning: “store this representation at this URL, replacing whatever is there.”

PATCH came much later. It was not part of the original HTTP/1.1 specification (RFC 2616, 1999) at all. Developers spent years abusing POST or misusing PUT for partial updates because there was simply no official “update just a piece of this resource” verb. The gap became painful enough that in 2010, the IETF published RFC 5789, which formally added PATCH to HTTP as a method for applying partial modifications to a resource.

Plain-English Definition

PUT means “replace the whole thing with what I’m sending you.” PATCH means “apply this small change to the thing that’s already there.”

Why the history matters for how you use them today

Because PUT arrived first and was baked into the core HTTP/1.1 specification (RFC 2616, later refined by RFC 7231), it enjoys universal, rock-solid support across every HTTP library, proxy, browser, and framework ever built. Its behaviour is unambiguous and has not changed in over two decades.

PATCH, on the other hand, arrived as an add-on RFC. That meant for years, some HTTP client libraries, corporate proxies, and even a few web frameworks did not recognise it at all, or blocked it as an “unknown method.” This historical gap is part of why so many older APIs still use POST for partial updates — PATCH support simply was not reliable enough yet when those APIs were first designed. Today, PATCH support is essentially universal across modern tooling, but you will still meet legacy systems that never got around to adopting it.

It is also worth knowing that RFC 5789, the document that defines PATCH, does something unusual for an HTTP method specification: it deliberately does not define what the body of a PATCH request should look like. It only fixes the verb’s meaning (“apply a set of changes described in the request body”) and leaves the actual format of that description to other specifications. That single design choice is exactly why two separate, competing “patch document” formats exist today — JSON Patch and JSON Merge Patch — which we will explore in detail later in this guide.

02
Problem & Motivation

Why do we even need two different “update” verbs?

If PUT already existed and worked, why did the industry bother inventing PATCH at all? The answer starts with a very ordinary problem.

Picture a User resource in an API with ten fields: name, email, phone, address, avatar URL, bio, preferences, last-login time, role, and account status. Now imagine a mobile app just wants to update the user’s phone number — nothing else.

If the only tool available were PUT, the client would be forced to send all ten fields back to the server, even the nine that did not change. That creates real problems:

  • Wasted bandwidth — sending 10 fields to change 1 field, over and over, at scale, adds up to a lot of unnecessary network traffic.
  • Race conditions — if two people are editing different fields of the same user at the same time, whoever’s full-replace PUT lands second silently wipes out the first person’s change to a field they never even touched.
  • Fragile clients — the client must always fetch the complete, current object before it can safely send an update, adding an extra round trip and coupling the client tightly to the full shape of the resource.

This is the exact motivation that led to RFC 5789 and the PATCH method: give developers a standard, well-defined way to say “change just this one field” without re-sending or risking the rest of the resource.

PUT replaces the whole resource. PATCH nudges just a piece of it.

The “lost update” problem, illustrated

Let’s make the race-condition risk concrete with a timeline. Suppose two support agents, Alice and Bob, both open the same customer record at the same time:

1

Alice loads the record

It shows { name: "Sam", phone: "555-0001", plan: "basic" }.

2

Bob loads the same record moments later

He sees exactly the same data as Alice.

3

Alice updates the phone number

She sends a full-object PUT built from what she loaded: { name: "Sam", phone: "555-0002", plan: "basic" }.

4

Bob upgrades the plan — from stale data

Before Bob’s change goes out, he upgrades the plan in his local copy and sends his own full-object PUT: { name: "Sam", phone: "555-0001", plan: "premium" } — built from the stale data he loaded in step 2, which still has the old phone number.

5

Bob’s PUT lands after Alice’s

The result: the plan is now “premium” (Bob’s intended change survives) but the phone number has silently reverted to “555-0001” (Alice’s change is lost) — even though Bob never intended to touch the phone field at all.

This is called a lost update, and it is a direct, structural consequence of using full-resource replacement for what was conceptually a single-field change. Had Bob’s client instead sent PATCH { plan: "premium" }, Alice’s phone-number change would have been completely unaffected, because Bob’s request would never have mentioned the phone field at all.

This single scenario — two people, two fields, one shared resource — is the whole reason the industry needed a partial-update verb. It is not a hypothetical: it happens constantly in collaborative dashboards, admin panels, mobile apps with offline sync, and any system where more than one actor can touch the same record.

03
Core Concepts

The building blocks you need first

Before we go deeper, three ideas will do all the heavy lifting: resources, idempotency, and safety. Get these three right and the rest is mostly detail.

What is a “resource”?

In REST (REpresentational State Transfer, the architectural style most web APIs follow), everything the API exposes — a user, an order, a blog post — is called a resource, and each resource lives at its own URL, like /users/42. A “representation” is just the format the resource is sent in, usually JSON.

What does “idempotent” mean?

This is the single most important concept in this entire guide, so let’s build it up slowly with an analogy. Imagine a light switch that has only “ON” and “OFF” positions (not a toggle). If you press “turn the light ON” five times in a row, the light is ON — exactly the same result as pressing it once. That is idempotent: doing the operation multiple times produces the same end state as doing it once.

Now imagine a counter that increases by 1 every time you press a button. Press it five times and you get +5, not +1. That is not idempotent — repeating it changes the outcome each time.

MethodIdempotent?Meaning
GETYesRead-only, never changes anything.
PUTYesReplacing with the same full body twice = same end state.
DELETEYesDeleting something already deleted = still deleted.
PATCHNot guaranteedDepends entirely on what the patch instructions say.
POSTNoTypically creates a new resource each time.

PUT being idempotent is baked into the HTTP specification itself. PATCH’s idempotency is not guaranteed by the spec — it depends on what kind of change you are describing. “Set the email to x@example.com” is idempotent (run it 100 times, same result). “Increment the login count by 1” is not idempotent (run it 100 times, you get 100 extra logins).

!
Common Misconception

Many developers assume PATCH is “the non-idempotent one” and PUT is “the idempotent one,” as if it were a hard rule. In truth, PUT is always idempotent by definition. PATCH’s idempotency depends entirely on the specific operation being described.

What is “safety” in HTTP, and why doesn’t it apply here?

HTTP also defines a related but different concept called safety: a method is “safe” if it does not change server state at all — it is read-only. GET and HEAD are safe. Neither PUT nor PATCH is safe, since both are explicitly designed to change state. Do not confuse “safe” (no side effects at all) with “idempotent” (repeatable with the same end result) — they answer different questions.

What is a “representation”?

REST’s full name — REpresentational State Transfer — hints at an important nuance: the client never touches the actual resource (say, a row in a database) directly. It only ever sees and sends a representation of that resource, almost always as JSON in modern APIs, but historically also XML or form-encoded data. When you PUT or PATCH, you are really telling the server “here is a new representation — figure out how to make the real underlying resource match it.” This distinction matters because it explains why the server, not the client, is always responsible for translating a representation into actual storage changes (database writes, cache invalidations, and so on).

Statelessness and the “self-describing” request

Another core REST principle is statelessness: each HTTP request must contain everything the server needs to process it, without relying on any memory of prior requests. This is why both PUT and PATCH requests are complete and self-contained — a PUT carries the entire new state, and a PATCH carries the entire description of the change, so the server never needs to “remember” what a previous request in the same session looked like.

Idempotency vs. determinism — a subtle but useful distinction

It helps to separate two related ideas. Determinism means the same input always produces the same output. Idempotency means applying an operation multiple times has the same effect as applying it once. A PATCH that sets updated_at to “now” is deterministic in the sense that it always sets a timestamp — but it is not idempotent, because each repeated call produces a different value (a different “now”). Keeping these two concepts distinct helps you avoid a common trap: assuming that because an operation always “does the same kind of thing,” it is automatically safe to retry.

04
Architecture & Anatomy

What these requests look like on the wire

Let’s look at the exact bytes a client sends the server when updating a user’s phone number — first with PUT, then with PATCH’s two competing body formats.

PUT — full replacement

http — put request
PUT /users/42 HTTP/1.1
Host: api.example.com
Content-Type: application/json

{
  "name": "Maria Chen",
  "email": "maria@example.com",
  "phone": "+1-555-0199",
  "address": "12 Willow St",
  "role": "member"
}

Notice every field is present — even the ones that did not change. The server’s contract for PUT is: “whatever you send me becomes the new, complete state of this resource.” If you accidentally omit the address field, a strict PUT implementation may wipe it out entirely.

PATCH — partial update

PATCH does not have one fixed body format — RFC 5789 deliberately leaves the format open, which is why two different “patch document” standards emerged:

RFC 7396

JSON Merge Patch

Send only the fields you want changed, as a partial JSON object. Simple, but can’t express array insertions or “remove middle element” cleanly.

RFC 6902

JSON Patch

Send an explicit list of operations: add, remove, replace, move, copy, test. More powerful and precise, slightly more verbose.

http — two patch flavours
// JSON Merge Patch — just the changed field
PATCH /users/42 HTTP/1.1
Content-Type: application/merge-patch+json

{ "phone": "+1-555-0199" }

// JSON Patch — explicit operation list
PATCH /users/42 HTTP/1.1
Content-Type: application/json-patch+json

[
  { "op": "replace", "path": "/phone", "value": "+1-555-0199" }
]
Rule of Thumb

If you only need to overwrite fields, JSON Merge Patch is simpler. If you need to insert into arrays, delete specific elements, or run atomic multi-step edits, JSON Patch’s operation list gives you far more control.

Why the Content-Type header matters so much for PATCH

Unlike PUT, where the Content-Type is almost always simply application/json, a well-built PATCH endpoint should pay close attention to the Content-Type header, because it is the only signal telling the server which patch format to expect. A server that supports both formats typically branches its parsing logic based on this header:

java — spring controller
@PatchMapping(value = "/users/{id}", consumes = {
        "application/merge-patch+json",
        "application/json-patch+json"
})
public ResponseEntity<User> patchUser(
        @PathVariable Long id,
        @RequestHeader("Content-Type") String contentType,
        @RequestBody String rawBody) {

    User existing = userRepository.findById(id).orElseThrow();

    if (contentType.contains("json-patch")) {
        applyJsonPatchOperations(existing, rawBody);
    } else {
        applyMergePatch(existing, rawBody);
    }

    validateMergedResult(existing);
    userRepository.save(existing);
    return ResponseEntity.ok(existing);
}

If a client omits the header or sends a generic application/json, most APIs fall back to treating the body as a JSON Merge Patch, since that format’s syntax (a plain partial object) is the more intuitive default. Clear API documentation should always state explicitly which format — or formats — an endpoint accepts, since guessing wrong produces confusing, hard-to-debug results rather than an obvious error.

Nested objects and arrays

JSON Merge Patch has one especially important quirk worth calling out: when a patch fragment includes a nested object, that nested object is merged recursively, field by field. But when a patch fragment includes an array, the entire array is replaced wholesale — there is no concept of “insert into the middle” or “merge array elements” in the merge-patch spec. This is precisely the limitation that pushes teams with array-heavy resources (like a list of line items on an order) toward JSON Patch instead, since its add, remove, and move operations can target a specific array index directly.

05
Internal Working

What happens inside the server

From the server’s point of view, the two methods trigger very different internal logic. Here is the shape of both, side by side.

Handling PUT internally

  1. Parse the full incoming JSON body.
  2. Validate it against the complete schema (every required field must be present).
  3. Either overwrite the existing row/document entirely, or create it if it does not exist yet (PUT is allowed to double as “create at this specific URL”).
  4. Persist and return 200 OK (updated) or 201 Created (new).

Handling PATCH internally

  1. Fetch the current, existing state of the resource from storage.
  2. Parse the incoming patch document (merge-patch object or JSON Patch operation list).
  3. Apply each change on top of the existing state, in memory.
  4. Validate the resulting merged object, not just the patch fragment.
  5. Persist the merged result and return 200 OK.

Here is a simplified Java example showing the internal difference using a Spring-style controller:

java — put vs patch handler
@PutMapping("/users/{id}")
public ResponseEntity<User> replaceUser(
        @PathVariable Long id, @RequestBody User fullUser) {
    // fullUser must contain ALL fields — this REPLACES the record
    fullUser.setId(id);
    userRepository.save(fullUser); // overwrite entirely
    return ResponseEntity.ok(fullUser);
}

@PatchMapping("/users/{id}")
public ResponseEntity<User> updateUser(
        @PathVariable Long id, @RequestBody Map<String, Object> updates) {
    User existing = userRepository.findById(id)
        .orElseThrow(() -> new ResourceNotFoundException(id));

    // Only touch the fields present in the patch body
    updates.forEach((key, value) -> {
        switch (key) {
            case "phone"   -> existing.setPhone((String) value);
            case "email"   -> existing.setEmail((String) value);
            case "address" -> existing.setAddress((String) value);
            // ... other fields left untouched
        }
    });

    userRepository.save(existing);
    return ResponseEntity.ok(existing);
}

Handling JSON Patch (RFC 6902) operations internally

When a server accepts the more expressive JSON Patch format, it must implement each operation type as its own small piece of logic. Here is what a minimal operation-applier looks like conceptually in Java:

java — json patch applier
public class JsonPatchApplier {

    public void apply(User target, List<PatchOperation> ops) {
        for (PatchOperation op : ops) {
            switch (op.getOp()) {
                case "replace" -> setField(target, op.getPath(), op.getValue());
                case "remove"  -> setField(target, op.getPath(), null);
                case "add"     -> addToField(target, op.getPath(), op.getValue());
                case "test"    -> verifyField(target, op.getPath(), op.getValue());
                default -> throw new UnsupportedOperationException(op.getOp());
            }
        }
    }
}

The test operation deserves a special mention: it lets the client assert “this field must currently equal X” as a precondition within the same patch document. If the assertion fails, the entire patch is rejected atomically — none of the other operations in the list are applied. This gives JSON Patch a built-in, fine-grained alternative to the ETag-based concurrency control we will cover in the reliability section.

Why validation order matters

A subtle but important internal detail: servers must validate the merged, final object, never just the incoming patch fragment in isolation. Consider a resource with a business rule “end_date must be after start_date.” A patch that only changes end_date looks perfectly valid on its own — but it could violate the rule once merged with the resource’s existing, untouched start_date. Correct internal handling therefore always looks like: fetch → merge → validate merged result → persist, never fetch → validate fragment → merge → persist.

06
Data Flow & Lifecycle

Following a request from client to database

The client sends the same-looking HTTP call in both cases — but under the hood, the two methods take very different paths to the database.

CLIENT SERVER DATABASE PUT (full replace) PUT /users/42 (whole object) overwrite row 42 200 OK PATCH (partial update) PATCH /users/42 (delta) SELECT row 42 current state merge + validate UPDATE row 42 200 OK
PUT is one write. PATCH is a read, then a merge, then a write.

The key lifecycle difference: PUT never needs to read the existing state first — it just overwrites. PATCH almost always needs a read-then-write cycle, because it has to know what the current values are before it can merge changes on top of them. That read-then-write pattern is also exactly why PATCH is more vulnerable to race conditions unless it is protected (more on that in the reliability section).

Status codes across the lifecycle

Both methods can return several different status codes depending on where in the lifecycle something goes right or wrong:

StatusWhen it applies to PUTWhen it applies to PATCH
200 OKExisting resource successfully replacedExisting resource successfully patched
201 CreatedResource didn’t exist yet, created at this URLNot typical — PATCH usually targets existing resources
204 No ContentUpdate succeeded, no body returnedUpdate succeeded, no body returned
404 Not FoundRare — many servers auto-create on PUT insteadCommon — can’t partially update something that doesn’t exist
409 ConflictResource state conflicts (e.g. version mismatch)Same, especially with concurrency control
412 Precondition FailedIf-Match / If-Unmodified-Since header didn’t matchExtremely common in production PATCH implementations
422 Unprocessable EntityBody is well-formed but fails validationMerged result fails validation

Notice that 404 Not Found behaves asymmetrically: it is unusual for PUT (since PUT is often allowed to create), but completely ordinary for PATCH, because you cannot logically apply a change to something that is not there yet — there is nothing to merge onto.

What should the response body contain?

Best practice for both methods is to return the complete, updated resource in the response body after a successful update. For PUT this is almost redundant (the client already knows what it sent), but it is still valuable because the server may have applied defaults, computed fields, or timestamps. For PATCH, returning the full updated resource is especially valuable, since the client sent only a fragment and may not otherwise know the resulting full state without an extra GET.

07
Trade-offs

Weighing the two approaches

Every design choice has a cost. Here is the honest tally for each method, followed by a simple decision framework.

PUT — Advantages

  • Simple, predictable semantics: what you send is exactly what you get back.
  • Always idempotent by specification — safe to retry blindly.
  • Easy to reason about and easy to cache/validate against a schema.
  • Can double as “create if not exists” at a client-chosen URL.

PUT — Disadvantages

  • Wasteful for small changes on large resources.
  • Risk of accidentally clobbering fields the client didn’t intend to touch.
  • Requires the client to have the full, current object before updating.

PATCH — Advantages

  • Efficient: send only what changed, saving bandwidth.
  • Reduces risk of one client overwriting another client’s unrelated changes.
  • Great fit for mobile apps and high-frequency, small edits.

PATCH — Disadvantages

  • No single standard body format (merge patch vs JSON Patch) can confuse API consumers.
  • Idempotency isn’t guaranteed — you must design it carefully.
  • More server-side complexity: read-merge-validate-write instead of just write.
  • Harder to cache or validate generically.

A simple decision framework

When you are deciding which method to expose for a given endpoint, three questions usually settle it:

  1. How big is the resource, and how often does only a small part of it change? Small resources (a handful of fields) rarely benefit much from PATCH. Large resources with frequent single-field edits benefit a lot.
  2. Do multiple independent clients edit the same resource concurrently? If yes, PATCH’s narrower blast radius meaningfully reduces lost-update risk even before you add concurrency control.
  3. Can your team commit to the extra validation and concurrency work PATCH requires? PATCH is not “free” efficiency — it comes with real engineering cost. Teams that add PATCH without also adding proper merge validation and concurrency guards often end up with subtly buggier APIs than if they had stuck with PUT.

Many mature APIs simply support both: PUT for clients that already have the full object and want a simple, guaranteed-idempotent replace, and PATCH for clients making small, targeted edits. There is no rule that says you must pick only one.

08
Performance & Scale

How the choice affects performance at scale

At small scale, sending 10 fields versus 1 is meaningless. At millions of requests per day, that same difference starts showing up in the bandwidth bill.

At small scale, the difference between sending 10 fields versus 1 field is meaningless. At scale — millions of requests per day — it starts to matter in a few concrete ways:

~90%
less payload for a 1-field change on a 10-field resource with PATCH
1
extra DB read PATCH typically requires that PUT does not
round trips if clients GET-then-PUT to avoid clobbering

PUT trades network efficiency for server simplicity: a single write, no merge logic, no extra read. PATCH trades server simplicity for network efficiency: smaller payloads, but an extra read and merge step on every request. At very high write throughput, that extra read can add real load to a database, which is why some high-scale systems implement PATCH using atomic, field-level database updates (for example a SQL UPDATE ... SET phone = ?) instead of a full read-merge-write cycle in application code — this avoids the read entirely and pushes the “patch” logic down into the database layer.

Mobile and low-bandwidth clients

The performance argument for PATCH is strongest on mobile networks, where every kilobyte and every round trip has a real, felt cost for the end user — think spotty 4G in a moving car or a train tunnel. A messaging app that lets users edit a profile field while offline benefits enormously from being able to sync just the changed field once connectivity returns, rather than re-uploading an entire profile object that might include a large embedded avatar or bio.

Batch and bulk updates

At true scale, individual PUT or PATCH calls — one HTTP request per resource — become a bottleneck of their own, regardless of payload size, simply because of per-request overhead (TLS handshakes, connection setup, request parsing). Some high-throughput APIs introduce a bulk PATCH endpoint that accepts an array of targeted changes in a single request:

http — bulk patch pattern
PATCH /users/bulk HTTP/1.1
Content-Type: application/json

[
  { "id": 42, "changes": { "phone": "+1-555-0199" } },
  { "id": 43, "changes": { "plan":  "premium"    } }
]

This is not part of any official RFC — it is an application-level convention — but it illustrates how the core idea behind PATCH (send only what changed) can be extended to reduce network overhead even further at scale.

09
HA & Retries

Why idempotency matters so much for reliability

Networks are unreliable. Idempotency is the property that lets a client retry blindly without corrupting your data.

Networks are unreliable. A client might send a request, the server might process it successfully, but the response gets lost on the way back. The client, seeing no response, does not know if the update happened — so it retries.

If the operation is idempotent, retrying is completely safe: applying the same change twice has the same effect as applying it once. This is why load balancers, API gateways, and client SDKs can automatically retry PUT requests without any special precautions.

PATCH is trickier. If a patch says “set status to ‘shipped’,” retrying it is harmless. But if a patch says “append this item to the cart” or “increment retry_count by 1,” retrying it after a lost response will apply the change twice — a real bug.

!
Design Tip

Prefer “set field to value”-style patches over “increment/append” style patches. If increments or appends are unavoidable, use an idempotency key (a unique request ID the server remembers) so a retried request with the same key is recognised and ignored instead of applied twice.

Optimistic concurrency control

To prevent the “two people editing at once” race condition, production APIs often combine PATCH with an ETag / If-Match header. The client first fetches the resource and notes its version tag (ETag). When it sends the PATCH, it includes If-Match: <etag>. If the resource has changed since the client last read it, the server rejects the request with 412 Precondition Failed instead of silently applying a stale patch.

http — etag-guarded patch
GET /users/42 HTTP/1.1
→ 200 OK, ETag: "v7"

PATCH /users/42 HTTP/1.1
If-Match: "v7"
Content-Type: application/merge-patch+json

{ "phone": "+1-555-0199" }
→ 200 OK, ETag: "v8"          (if v7 still matches)
→ 412 Precondition Failed   (if someone else already changed it)

Idempotency keys for non-idempotent PATCH operations

When a PATCH operation genuinely cannot be made idempotent by rewording it (a payment API “charge an additional $5 to this invoice” is a good example — it is inherently an increment), the standard fix is an idempotency key: a unique token the client generates once per logical operation and includes on every attempt, including retries.

http — idempotency key
PATCH /invoices/9001 HTTP/1.1
Idempotency-Key: 7f3d9e21-4c2b-4a10-9e77-1b0e2f0a55d1
Content-Type: application/json

{ "op": "add_charge", "amount": 5.00 }

The server stores a record of every idempotency key it has already processed, along with the result. If the same key arrives again — because the client’s original request timed out and it is retrying — the server recognises it and returns the same stored result instead of applying the charge a second time. This pattern, popularised by payment processors like Stripe, effectively makes any operation idempotent from the client’s point of view, regardless of what the operation itself does internally.

Retries at different layers of the stack

Reliability is not just a client concern — it shows up at every layer of a distributed system:

  • Client SDKs often auto-retry idempotent methods (PUT, GET, DELETE) on network timeouts, but deliberately do not auto-retry PATCH unless the developer explicitly marks the call as idempotent or supplies an idempotency key.
  • API gateways may implement their own retry policies for backend timeouts, and need the same idempotency awareness to avoid duplicating non-idempotent PATCH effects.
  • Message queues feeding into internal services (in an “at-least-once delivery” system) can redeliver the same PATCH-equivalent internal event more than once, which is why internal event handlers are often written to be idempotent regardless of what HTTP method originally triggered them.
Rule of Thumb

If you can’t confidently answer “what happens if this exact request is received twice?”, you’re not ready to expose it as a retryable PATCH endpoint — add an idempotency key or redesign the operation first.

10
Security

Security considerations for both methods

Update endpoints are one of the most abused surfaces on any API. Here is what to watch for — and how to fix it before an attacker does.

  • Mass assignment vulnerabilities: With PUT, if the server blindly trusts every field in the body, an attacker could include fields like "role": "admin" that the client should not be allowed to set. Always whitelist which fields are writable, regardless of method.
  • Authorisation must be checked per field for PATCH: a user might be allowed to PATCH their own bio field but not their role field — this needs explicit server-side checks since PATCH bodies are dynamic and partial.
  • Idempotency keys can leak state if not scoped to the authenticated user — always tie an idempotency key to the requesting client’s identity.
  • Input validation must run on the final merged object for PATCH, not just the incoming fragment, otherwise a valid-looking partial patch could push the overall resource into an invalid state (for example, patching discount to a value that, combined with an untouched price, produces a negative total).
Golden Rule

Never trust the client to decide the full shape of a resource. Validate against a strict schema on the server for PUT, and validate the post-merge result for PATCH — every single time.

A concrete mass-assignment example

Imagine a naive PUT handler that simply deserialises the request body straight into a database entity and saves it:

java — DANGEROUS
// DANGEROUS — do not do this
@PutMapping("/users/{id}")
public User replaceUser(@PathVariable Long id, @RequestBody User body) {
    body.setId(id);
    return userRepository.save(body); // blindly trusts every field, incl. "role"
}

If User has a role field, and the endpoint does not strip it out or check permissions, any authenticated user could send {"role": "admin", ...} and grant themselves administrator access. The fix is to use a restricted “input” model (a Data Transfer Object) that simply does not contain sensitive fields, or to explicitly whitelist which fields a given caller is authorised to set:

java — safe
@PutMapping("/users/{id}")
public User replaceUser(@PathVariable Long id, @RequestBody UserUpdateRequest body,
                         Authentication auth) {
    User existing = userRepository.findById(id).orElseThrow();
    if (!auth.getName().equals(existing.getUsername()) && !hasAdminRole(auth)) {
        throw new AccessDeniedException("Not authorized");
    }
    existing.setName(body.getName());
    existing.setEmail(body.getEmail());
    existing.setPhone(body.getPhone());
    // role is intentionally never settable through this endpoint
    return userRepository.save(existing);
}

Rate limiting and abuse prevention

Because PATCH can be cheap to send (small payloads), it can also be cheap to abuse — an attacker could hammer a PATCH endpoint with a rapid stream of tiny requests attempting to brute-force a field or exploit a race condition window. Standard mitigations apply: per-user and per-IP rate limiting, short-lived authentication tokens, and audit logging of who changed what and when.

11
Monitoring

What to watch in production

PUT and PATCH deserve different dashboards. Here are the signals that catch trouble early.

Because PUT and PATCH behave differently, they deserve different monitoring signals:

payload

Request size

Track average PUT payload size — a growing average may mean clients are fetching more fields than needed before replacing.

concurrency

412 rate

A rising rate of 412 Precondition Failed on PATCH indicates real concurrent-edit contention worth investigating.

reliability

Retry rate

High retry counts on non-idempotent PATCH endpoints are a red flag for potential duplicate side effects.

audit

Field-level audit logs

Log which fields were actually changed by each PATCH, both for debugging and for compliance trails.

It is also good practice to log the diff (before/after) for every PATCH request, since — unlike PUT — you cannot reconstruct “what changed” just by looking at the request body alone if it is a JSON Patch with operations like move or test.

Building a useful dashboard

A practical monitoring dashboard for update endpoints typically tracks, per-endpoint and per-method:

  • Request rate and latency percentiles (p50/p95/p99) — PATCH latency should generally be watched separately from PUT, since PATCH’s extra read step can create a different latency profile under load.
  • Error rate broken down by status code — a spike specifically in 422 for PATCH often signals a client bug sending fields the server does not recognise, while a spike in 412 signals real concurrency contention.
  • Payload size distribution — useful for catching PUT requests that have crept up in size over time as a resource’s schema grows.
  • Field-change frequency — tracking which fields get PATCHed most often can reveal opportunities to split a resource into smaller, independently-updatable sub-resources.
Practical Tip

Emit a structured log line for every successful write that includes the method, resource ID, actor, and the set of field names that actually changed. This single log format answers most “who changed X and when” support questions without needing a full audit-trail system.

12
Deployment

How cloud platforms and API gateways treat these methods

The verbs travel unchanged through infrastructure — but the way infrastructure treats them is not identical.

Cloud API gateways (AWS API Gateway, Google Cloud API Gateway, Azure API Management) route both PUT and PATCH like any other HTTP verb, but a few deployment-level details matter:

  • Idempotent-method retries at the gateway/load-balancer level are often enabled by default for PUT, GET, and DELETE, but NOT automatically enabled for PATCH — because the infrastructure cannot know if your specific PATCH is idempotent.
  • Infrastructure-as-code tools (Terraform, Kubernetes controllers) heavily favour PUT-style semantics internally — a Kubernetes kubectl apply is conceptually closer to a PUT/merge-patch hybrid that reconciles a desired full state, while kubectl patch exposes an explicit PATCH-style partial update.
  • API versioning matters more for PATCH bodies, since the shape of a JSON Patch operation list or merge-patch fragment can evolve independently of the resource schema.

Rolling deployments and backward compatibility

During a rolling deployment, some server instances may be running an old version of your code while others run the new version. This is especially risky for PATCH endpoints if you are changing what fields are patchable, since a request handled by an old instance might silently ignore a new field that a newer client is trying to set. A safe rollout pattern is to first deploy the server-side support for a new patchable field, confirm it is live everywhere, and only then update clients to start sending it — never the reverse order.

Content negotiation in the gateway

Because PATCH supports multiple body formats, some API gateways use the Content-Type header (application/json-patch+json vs application/merge-patch+json) to route requests to different backend handlers, or to reject unsupported formats early with a 415 Unsupported Media Type response before the request even reaches application code — saving backend compute on malformed requests.

13
Data & Load Balancing

Storage and caching implications

The two verbs land differently on your database, your caches, and your load balancers — and each of those layers has a preferred way of being spoken to.

Database layer

A naive PATCH implementation does SELECT then UPDATE — two round trips and a window for race conditions. A more scalable approach pushes the partial update directly into SQL:

sql — naive vs atomic
-- Naive: read, merge in app code, write back the whole row
SELECT * FROM users WHERE id = 42;
-- ...merge phone field in Java...
UPDATE users SET name=?, email=?, phone=?, address=? WHERE id=42;

-- Better: atomic, field-level update, no read needed
UPDATE users SET phone = ? WHERE id = 42;

The second form is not just faster — it is also naturally safer under concurrency, since the database handles the row lock internally rather than the application juggling a read-then-write window.

Caching

Both PUT and PATCH should invalidate any cached representation of the resource (CDN cache, application cache, or HTTP cache tied to the resource’s ETag). Since PUT sends the complete new state, cache invalidation is simple — just overwrite the cached value with the new body. PATCH requires the cache to either be invalidated (forcing a fresh fetch) or updated with the same merge logic the server used, to avoid serving a stale cached copy.

Load balancing

Because PUT is always safe to retry, load balancers can safely resend a PUT to a different backend instance after a timeout. PATCH requests are riskier to blindly resend unless the backend guarantees idempotency (often via an idempotency key passed through the load balancer to whichever instance handles the retry).

Read replicas and eventual consistency

In systems that use read replicas (a common pattern for scaling database reads), a PATCH’s read-then-write cycle introduces an extra subtlety: if the “read” half of a PATCH is served from a replica that lags slightly behind the primary database, the server could merge changes onto stale data without realising it. Production systems typically route the read portion of any read-modify-write operation to the primary database (not a replica) specifically to avoid this class of bug, even though that sacrifices some of the read-scaling benefit replicas normally provide.

Row-level locking

At the database layer, an atomic UPDATE ... SET field = ? WHERE id = ? naturally takes a short-lived row lock for the duration of the write, which is enough to prevent two simultaneous PATCH requests from corrupting each other at the storage layer — though it does nothing to prevent the higher-level “lost update” problem described earlier, which requires application-level concurrency control like ETags.

14
Microservices

Choosing methods across service boundaries

In a microservices world, an update rarely lives inside just one service. The verb the outside world sees can be very different from the verbs the inside world uses.

In a microservices architecture, resources are often owned by different services, and a single logical “update” might touch several of them. A common pattern:

Client Order Service PATCH /orders/9 Order DB Event Bus Inventory Notifications PATCH write event
Client-facing PATCH, internal event fan-out to downstream services.

Here, the client-facing API exposes PATCH for a good developer experience (small, targeted updates), while internally the Order Service may use a full-replace write against its own database for simplicity, then publish an event so other services can react to just the fields that changed. This is a common and healthy split: expose partial updates externally, but keep internal writes as simple as possible.

API gateways aggregating multiple services

Some architectures use a gateway or “backend for frontend” (BFF) layer that exposes one clean, partial-update endpoint to clients but internally fans that single PATCH out into multiple calls against different downstream services — for instance, a PATCH to /profile/42 might update the User Service’s name field and the Preferences Service’s notification settings in the same logical request. This gives clients a simple mental model while letting the backend stay decomposed into smaller services.

Event sourcing as an alternative to PATCH

Some systems avoid PATCH-style mutation entirely in favour of an event-sourced design: instead of ever updating a stored record in place, every change is recorded as an immutable event (“PhoneNumberChanged,” “PlanUpgraded”), and the current state is derived by replaying all events. In such systems, the HTTP-facing PATCH endpoint still looks like a normal partial update from the client’s perspective, but internally it is translated into “append a new event” rather than “mutate a row” — side-stepping lost-update races entirely, since nothing is ever overwritten, only appended to.

15
Patterns

Patterns worth copying, and traps worth avoiding

Once you have shipped a few real APIs, the same handful of patterns and anti-patterns keep showing up. Here are the ones worth committing to memory.

Good patterns

  • Command-style sub-resources: instead of a risky “increment” PATCH, model the action as its own endpoint: POST /users/42/login-events instead of PATCH-incrementing a login counter. This side-steps the idempotency problem entirely.
  • ETag-guarded PATCH: always pair PATCH with optimistic concurrency control via If-Match, as covered earlier.
  • Strict merge-patch schemas: validate incoming PATCH fragments against a schema that only allows known, patchable fields.
  • Field-level permission maps: maintain an explicit table of “which roles can PATCH which fields” rather than checking permissions ad hoc inside a large switch statement — this scales much better as a resource grows more fields.
  • Idempotent-by-construction operations: when a change is naturally an increment (like a view counter), model it as its own dedicated, non-generic endpoint (POST /articles/9/views) rather than trying to force it through generic PATCH semantics.

Anti-patterns to avoid

!
Anti-pattern — “PUT for partial updates”

Sending a PUT with only some fields and expecting the server to leave the rest untouched violates the HTTP spec’s meaning of PUT and creates unpredictable behaviour between different server implementations — some will null out missing fields, others won’t.

!
Anti-pattern — “PATCH as a dumping ground”

Using PATCH to smuggle in arbitrary, loosely-validated operations (including ones that change unrelated resources) breaks the principle that PATCH should modify one resource at one URL.

16
Best Practices

A practical checklist

Six things you will thank yourself for the next time an on-call page wakes you up.

1

Use PUT for full replacement, PATCH for partial updates

Don’t blur this line just for convenience — it breaks client expectations and caching behaviour.

2

Pick one PATCH format and document it clearly

JSON Merge Patch for simple cases, JSON Patch when you need array operations or atomic multi-field changes.

3

Validate the merged result, not just the fragment

A partial patch can still produce an invalid whole resource.

4

Guard against lost updates with ETags

Especially important for PATCH, where read-then-write windows are common.

5

Design PATCH operations to be idempotent where possible

Favour “set” semantics over “increment”/“append” semantics.

6

Return the full updated resource in the response

Saves the client an extra GET and confirms exactly what the server applied.

Common mistake: forgetting that PUT is allowed to create a resource if it does not already exist at that URL (returning 201 Created), which trips up developers who assume PUT only ever updates.

Common mistake: treating a missing field in a PATCH body as “set it to null/empty” instead of “leave it untouched.” This single misunderstanding is responsible for a large share of real-world PATCH bugs — a client that simply does not send an address field is not saying “clear the address,” it is saying “I have nothing to say about the address.” Getting this distinction right in your server code is non-negotiable for correct PATCH behaviour.

Common mistake: not documenting which fields are immutable. Some fields — like a resource’s ID, creation timestamp, or a foreign key set at creation time — should never be patchable at all. Silently ignoring attempts to change them is confusing; explicitly rejecting such attempts with a clear 422 error and message is far more developer-friendly.

17
Real World

How major companies use PUT and PATCH

Theory only takes you so far. Here is what four industry giants actually ship.

github

GitHub API

Uses PATCH extensively for updating issues, pull requests, and repositories — you can change just the “state” field of an issue without resending its title and body.

stripe

Stripe API

Update endpoints (e.g., updating a Customer object) accept partial parameters, functioning like PATCH semantics even where the underlying HTTP verb historically used POST for compatibility.

kubernetes

Kubernetes API

Explicitly supports several PATCH strategies (JSON Patch, Merge Patch, and a Kubernetes-specific Strategic Merge Patch) because config resources are large and usually only one field changes at a time.

aws

AWS APIs

Many resource-update calls (e.g., updating an S3 bucket’s specific configuration) are modelled as targeted, single-purpose calls rather than generic PUT/PATCH, but conceptually follow the same “touch only what changed” philosophy PATCH represents.

The common thread: as a resource grows larger and more frequently updated in small ways, the pressure to move from PUT to PATCH grows with it. Small, rarely-changing resources often stick with simple PUT-based replace semantics because the added complexity of PATCH is not worth it.

A closer look: GitHub’s issue-update endpoint

GitHub’s REST API lets you PATCH an issue to change just its state field from “open” to “closed” without touching the title, body, labels, or assignees. This is a textbook case for PATCH: issues are long-lived, frequently touched by many different actors (the reporter, multiple commenters, automated bots that add labels), and a full-replace PUT would create constant risk of one actor’s automated label change being wiped out by another actor’s unrelated status update landing at nearly the same moment.

A closer look: Kubernetes’ three patch strategies

Kubernetes is a particularly instructive example because it supports three different patch strategies for the same underlying resources, chosen based on how safely they merge:

rfc 7396

JSON Merge Patch

Simple field overwrite, same as RFC 7396 — but it replaces entire arrays rather than merging their elements.

rfc 6902

JSON Patch

Explicit operation list, giving precise control over array insertions and removals when needed.

k8s-only

Strategic Merge Patch

A Kubernetes-specific format that understands the semantic meaning of certain array fields (like merging a list of containers by name instead of blindly replacing the whole list) — solving JSON Merge Patch’s biggest weakness for Kubernetes’ specific data shapes.

principle

The takeaway

“PATCH” is not a single fixed technology — it is a pattern real systems adapt and extend to fit their own data models when the standard formats aren’t expressive enough.

18
FAQ

Frequently asked questions

The questions that keep coming up on Stack Overflow, in code reviews, and in every API-design meeting.

Can PUT be used to create a resource?

Yes. If the client specifies the exact URL a new resource should live at (e.g., PUT /users/42 for a resource that does not exist yet), the server can create it and respond with 201 Created. This is different from POST, where the server typically chooses the new resource’s URL.

Is PATCH always more efficient than PUT?

For large resources with small, frequent changes, yes. For small resources, the difference is negligible, and PUT’s simplicity may be preferable.

Which one should a new API default to?

Support PUT for full replacements and add PATCH once you have a real, recurring need for partial updates — don’t build PATCH support speculatively before you need it.

Does PATCH always require reading the resource first?

Not necessarily — if the update can be expressed as a direct database-level operation (like a SQL UPDATE on a single column), no application-level read is required. The “read-merge-write” pattern is common but not mandatory.

Is POST ever a substitute for PATCH?

Some APIs (especially older ones) use POST for partial updates because PATCH was not well-supported by older HTTP libraries or proxies. It works, but it loses the semantic clarity and the idempotency guarantees that come with correctly using PATCH.

Can you send null to remove a field with PATCH?

It depends on the format. With JSON Merge Patch (RFC 7396), setting a field to null is explicitly defined as “delete this field” — there is no other way to express deletion in that format. With JSON Patch (RFC 6902), deletion is its own explicit operation type ({"op": "remove", "path": "/phone"}), so a null value is treated as an ordinary value, not a delete instruction. Mixing up these two conventions is a very common source of bugs when a team switches from one patch format to the other.

Does PATCH work with query parameters instead of a body?

Technically HTTP does not forbid it, but it is considered poor practice. PATCH’s entire value proposition is a structured, self-describing body that clearly states what should change; cramming that into query parameters loses type safety, has URL-length limits, and does not support the richer JSON Patch operation format at all.

How does PATCH interact with HTTP caching?

Like PUT, a successful PATCH should invalidate any cached GET response for that same resource URL. Well-behaved HTTP caches do this automatically when they see a successful, non-error response to an unsafe method against a URL they have cached — but only if the cache is aware of that URL’s prior GET response, which is why consistent, canonical resource URLs matter for caching correctness.

What’s the difference between PATCH and the older, less common HTTP method MERGE?

MERGE was a non-standard, WebDAV-related method used by a small number of early systems (notably some early OData implementations) before PATCH was standardised. It served a similar conceptual purpose but was never part of core HTTP and has been almost entirely superseded by standardised PATCH usage today.

19
Takeaways

Summary & Key Takeaways

Remember This

  • PUT replaces an entire resource with what the client sends. It is simple, predictable, and always idempotent by specification.
  • PATCH applies a partial change to a resource, saving bandwidth and reducing accidental overwrites — but its idempotency depends on the operation and must be designed for deliberately.
  • PATCH typically needs a read-merge-write cycle (or an atomic database-level update) and requires validating the merged result, not just the incoming fragment.
  • Use ETag / If-Match with PATCH to prevent lost updates when multiple clients edit the same resource concurrently.
  • Favour “set”-style PATCH operations over “increment/append” style ones to keep them idempotent and safe to retry.
  • Real-world systems (GitHub, Kubernetes, Stripe) reach for PATCH specifically when resources are large and updates are small and frequent.
  • Neither method is universally “better” — the right choice depends on resource size, update frequency, concurrency risk, and how much engineering effort your team can invest in getting partial-update semantics right.
  • If you remember nothing else, remember the notebook analogy from the very first section: PUT rewrites the whole card, PATCH edits just one line. Everything about idempotency, concurrency control, validation order, and caching flows naturally from that one simple picture.