What is the difference between PUT and PATCH
Both PUT and PATCH are HTTP methods used to update existing resources on a server, but they possess fundamentally different architectural semantics, operational scopes, and data...
🟢 Junior Level
Both PUT and PATCH are HTTP methods used to update existing resources on a server, but they possess fundamentally different architectural semantics, operational scopes, and data-handling rules:
Core Conceptual Differences
PUT(Complete Resource Replacement — RFC 9110):- The client provides a complete representation of the target resource.
- Any existing fields that are omitted from the
PUTpayload must be cleared (set tonull) or reset to schema default values. - The method is strictly idempotent ($f(f(x)) = f(x)$).
- SQL Analogy:
UPDATE users SET name = ?, age = ?, email = ? WHERE id = ?;
PATCH(Partial Resource Modification — RFC 5789):- The client transmits only the specific fields or mutation instructions that need to change.
- All omitted fields retain their existing values in the database.
- Minimizes network payload size when modifying large objects with dozens of attributes.
- The method is not guaranteed to be idempotent (depends on the nature of the patch operation).
- SQL Analogy:
UPDATE users SET age = ? WHERE id = ?;
Concrete Request-Response Walkthrough
Assume the database currently holds User 42:
{
"id": 42,
"name": "Alex",
"age": 25,
"email": "alex@example.com"
}
Scenario A: Executing a PUT Request (Full Replacement)
PUT /api/v1/users/42 HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"name": "Alex",
"age": 26
}
Database Result: age is updated to 26, but because email was omitted from the complete replacement payload, the database resets email to null!
Scenario B: Executing a PATCH Request (Partial Update)
PATCH /api/v1/users/42 HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"age": 26
}
Database Result: Only age is updated to 26; email remains untouched as "alex@example.com".
🟡 Middle Level
Specification Comparison Matrix: RFC 9110 vs. RFC 5789
| Architectural Criterion | PUT (RFC 9110) |
PATCH (RFC 5789) |
|---|---|---|
| Semantic Intent | Complete replacement of target resource representation | Application of partial delta mutations to target resource |
| Idempotency Guarantee | ✅ Guaranteed Idempotent | ❌ Non-idempotent by default (operation-dependent) |
| Safety (Safe Method) | ❌ Unsafe (causes database mutations) | ❌ Unsafe (causes database mutations) |
| Standard Media Types | application/json, application/xml |
application/merge-patch+json, application/json-patch+json |
| Creation Capability | Can create a resource if the URI is client-assigned | Purely designed for modifying existing resources |
| Bandwidth Consumption | High (transfers full entity representation) | Low (transfers only field deltas) |
The Two Official IETF Standards for PATCH
The HTTP working group established two formal specifications for implementing partial updates:
1. JSON Merge Patch (RFC 7386) — Declarative Delta
The client transmits a partial JSON object using Content-Type: application/merge-patch+json. The server merges the supplied keys directly over the existing entity representation:
{
"email": "alex.new@example.com",
"phoneNumber": null
}
- In RFC 7386, an explicit
"field": nullinstruction directs the server to delete or clear that attribute, whereas omitting a field directs the server to leave it untouched.
2. JSON Patch (RFC 6902) — Imperative Mutation Sequence
The client transmits an ordered array of atomic operation instructions using Content-Type: application/json-patch+json and JSON Pointer (RFC 6901) syntax:
[
{ "op": "test", "path": "/version", "value": 4 },
{ "op": "replace", "path": "/age", "value": 26 },
{ "op": "remove", "path": "/temporaryToken" },
{ "op": "add", "path": "/tags/-", "value": "premium" }
]
- Supported operations:
add,remove,replace,move,copy, andtest. - If the
testoperation fails (e.g., entity version has changed), the entire patch transaction aborts atomically.
🔴 Senior Level
Production Implementation in Spring Boot 3
package com.example.controller;
import com.example.dto.ReplaceUserRequest;
import com.example.dto.UserResponseDto;
import com.example.service.UserService;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
// 1. PUT: Full representation replacement
@PutMapping("/{id}")
public ResponseEntity<UserResponseDto> replaceUser(
@PathVariable Long id,
@Valid @RequestBody ReplaceUserRequest request) {
return ResponseEntity.ok(userService.replaceUser(id, request));
}
// 2. PATCH: Partial update via JSON Merge Patch
@PatchMapping(path = "/{id}", consumes = "application/merge-patch+json")
public ResponseEntity<UserResponseDto> patchUser(
@PathVariable Long id,
@RequestBody JsonNode patchPayload) {
return ResponseEntity.ok(userService.patchUser(id, patchPayload));
}
}
package com.example.service;
import com.example.dto.ReplaceUserRequest;
import com.example.dto.UserResponseDto;
import com.example.entity.User;
import com.example.exception.ResourceNotFoundException;
import com.example.repository.UserRepository;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Transactional
public UserResponseDto replaceUser(Long id, ReplaceUserRequest req) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User not found: " + id));
// PUT completely overwrites all mutable fields, resetting omitted attributes to null
user.setName(req.name());
user.setAge(req.age());
user.setEmail(req.email()); // If req.email() is null, database value is cleared!
return UserResponseDto.fromEntity(user);
}
@Transactional
public UserResponseDto patchUser(Long id, JsonNode patchPayload) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User not found: " + id));
// Evaluate only fields explicitly supplied in the partial JSON payload
if (patchPayload.has("name")) {
user.setName(patchPayload.get("name").asText());
}
if (patchPayload.has("age")) {
user.setAge(patchPayload.get("age").asInt());
}
if (patchPayload.has("email")) {
// Distinguish explicit null from omitted field
user.setEmail(patchPayload.get("email").isNull() ? null : patchPayload.get("email").asText());
}
return UserResponseDto.fromEntity(user);
}
}
The “Three-State Null Problem” in Java DTOs
Standard Java Jackson deserialization maps both an omitted JSON property and an explicit "field": null to Java’s null. This prevents the application from discerning whether the client intended to clear the field or leave it untouched.
To solve this cleanly in type-safe DTOs, use JsonNullable<T> from org.openapitools.jackson.nullable.JsonNullable:
package com.example.dto;
import org.openapitools.jackson.nullable.JsonNullable;
public class PatchUserRequest {
// 1. Omitted from JSON: email.isUndefined() == true
// 2. Explicit "email": null: email.isPresent() == true && email.get() == null
// 3. Explicit "email": "x@y": email.isPresent() == true && email.get().equals("x@y")
private JsonNullable<String> email = JsonNullable.undefined();
private JsonNullable<Integer> age = JsonNullable.undefined();
// Getters and Setters
public JsonNullable<String> getEmail() { return email; }
public void setEmail(JsonNullable<String> email) { this.email = email; }
public JsonNullable<Integer> getAge() { return age; }
public void setAge(JsonNullable<Integer> age) { this.age = age; }
}
Why PATCH Can Be NON-IDEMPOTENT
While PUT is fundamentally idempotent, PATCH can easily break idempotency depending on its operational logic:
- Idempotent PATCH (Deterministic Property Replacement):
{ "op": "replace", "path": "/status", "value": "SUSPENDED" }Executing this request 1 time or 100 times results in the identical final state (
status = "SUSPENDED"). - Non-Idempotent PATCH (Array Append):
{ "op": "add", "path": "/roles/-", "value": "AUDITOR" }Each successive request appends another
"AUDITOR"entry to the end of therolesarray (["USER", "AUDITOR", "AUDITOR", "AUDITOR"]), modifying server state on every invocation. - Non-Idempotent PATCH (Numeric Increment):
{ "op": "increment", "path": "/wallet/balance", "value": 50 }Executing this 3 times increases the balance by 150 instead of 50.
4 Tricky Questions
1. What is the server obligated to do under RFC 9110 if a client executes a PUT request that omits an optional field?
Answer:
Under RFC 9110, PUT specifies a complete replacement of the target resource representation. The server is required to replace the existing stored resource with the exact representation supplied.
- Any fields omitted by the client must be cleared (set to
null) or reset to their default schema values. - The server must not assume omitted fields should retain their prior state. If the client wants to update only a subset of fields while keeping the others unchanged, it is an architectural error to issue a
PUT; the client must issue aPATCH.
2. Give two concrete examples where a PATCH request is non-idempotent.
Answer:
- JSON Patch Array Append:
[{"op": "add", "path": "/tags/-", "value": "featured"}]The-index denotes appending to the end of the array. Calling this endpoint 3 times appends 3 identical items to the list. - Relative Numeric Increments:
An operation modifying a counter or account balance via delta offsets (
{"op": "increment", "path": "/retryCount", "value": 1}). Each repeated execution increments the counter further, resulting in different server states.
3. How do RFC 7386 (JSON Merge Patch) and RFC 6902 (JSON Patch) differ architecturally?
Answer:
- RFC 7386 (JSON Merge Patch):
- Paradigm: Declarative state merge.
- Format: Standard JSON document containing target fields.
- Null Semantics: An explicit
nulldeletes the attribute; omitted attributes are preserved. - Limitation: Cannot partially modify arrays or manipulate individual list indices; arrays are replaced in their entirety.
- RFC 6902 (JSON Patch):
- Paradigm: Imperative sequence of transactional instructions.
- Format: An array of operation objects (
op,path,value). - Features: Supports fine-grained list manipulation (
add,remove,move,copy), conditional verification (test), and atomic transaction aborts if any step fails.
4. How do you resolve the “Three-State Null Problem” in Java Spring Boot applications?
Answer:
The problem is solved using JsonNullable<T> (from org.openapitools.jackson.nullable:jackson-databind-nullable):
JsonNullable.undefined()represents an attribute that was omitted from the incoming JSON payload (the service skips updating this column).JsonNullable.of(null)represents an attribute explicitly declared as"field": nullin the payload (the service sets the database column tonull).JsonNullable.of(value)represents a concrete updated value (the service updates the database column). Alternatively, developers can parse raw requests via Jackson’sJsonNodeand checkpatchNode.has("field")vspatchNode.get("field").isNull().
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
PUT(RFC 9110) is for complete resource replacement. The client supplies the entire representation; missing fields are reset tonullor defaults. It is strictly idempotent.PATCH(RFC 5789) is for partial resource updates. The client sends only modified fields or instructions, conserving network bandwidth. It is not guaranteed to be idempotent (e.g., array appends or increments).- Production standard implementations rely on RFC 7386 (JSON Merge Patch) for simple field merging or RFC 6902 (JSON Patch) for transactional instruction sequences. In Java, handle omitted vs. null values using
JsonNullable<T>.
Red Flags (What NOT to Say)
- ❌ “PUT and PATCH are identical synonyms; it is just a matter of team preference.” (A severe violation of HTTP specifications;
PUTreplaces,PATCHmodifies). - ❌ “PUT only updates the fields sent in the request and keeps the rest intact.” (This is the definition of
PATCH;PUTresets omitted fields). - ❌ “PATCH is always idempotent just like PUT.” (RFC 5789 explicitly notes that
PATCHis non-idempotent by default because operations like list appends alter state on repetition). - ❌ “There is no formal standard for PATCH in HTTP.” (RFC 5789 defines the method, RFC 7386 defines Merge Patch, and RFC 6902 defines JSON Patch).
Related Topics
- What are the main HTTP methods used in REST — HTTP verb matrix and semantics
- What is idempotency — Mathematical and network idempotency definitions
- Which HTTP methods are idempotent — Safe and idempotent method catalogs
- What is RESTful API design — Endpoint resource modeling principles