PUT vs PATCH

Replace or Update Partly

  • PUT replaces the whole resource. The client sends every field; a missing field becomes empty.
  • PATCH changes only the fields that are sent. Everything else stays as it was.
  • PUT is idempotent: sending the same request twice leaves the same result. PATCH is usually idempotent too, but does not have to be (e.g. "add 5 to stock").

Example

The difference in plain Java: PUT builds a new product from the request; PATCH copies only non-null fields onto the existing one.

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<String, Object> stored = new LinkedHashMap<>();
        stored.put("name", "Wireless Mouse");
        stored.put("description", "2.4 GHz, silent clicks");
        stored.put("price", 799);
        stored.put("stock", 120);

        Map<String, Object> request = new LinkedHashMap<>();
        request.put("price", 749);
        request.put("stock", 90);

        // PUT: the request IS the new resource
        Map<String, Object> afterPut = new LinkedHashMap<>();
        for (String field : stored.keySet()) afterPut.put(field, request.get(field));

        // PATCH: copy only the fields that were sent
        Map<String, Object> afterPatch = new LinkedHashMap<>(stored);
        request.forEach((field, value) -> { if (value != null) afterPatch.put(field, value); });

        System.out.println("stored      " + stored);
        System.out.println("after PUT   " + afterPut);
        System.out.println("after PATCH " + afterPatch);
    }
}

Output

stored      {name=Wireless Mouse, description=2.4 GHz, silent clicks, price=799, stock=120}
after PUT   {name=null, description=null, price=749, stock=90}
after PATCH {name=Wireless Mouse, description=2.4 GHz, silent clicks, price=749, stock=90}

PATCH in Spring

A simple and safe way: a request record whose fields are nullable wrapper types. null means "not sent, keep the old value".

public record ProductPatch(String name, String description,
                           BigDecimal price, Integer stock, String category) {}

@PatchMapping("/{id}")
public ResponseEntity<Product> patch(@PathVariable Long id, @RequestBody ProductPatch patch) {
    return service.find(id).map(old -> {
        Product updated = new Product(id,
                patch.name()        != null ? patch.name()        : old.name(),
                patch.description() != null ? patch.description() : old.description(),
                patch.price()       != null ? patch.price()       : old.price(),
                patch.stock()       != null ? patch.stock()       : old.stock(),
                patch.category()    != null ? patch.category()    : old.category());
        return ResponseEntity.ok(service.save(updated));
    }).orElse(ResponseEntity.notFound().build());
}

Example

curl -X PATCH localhost:8080/api/products/1 -H "Content-Type: application/json" -d '{"price":749.00}'

Example output

{"id":1,"name":"Wireless Mouse","description":"2.4 GHz, silent clicks","price":749.00,"stock":120,"category":"Electronics"}

Note: With this approach a client cannot set a field to null with PATCH. If you need that, use JSON Merge Patch (application/merge-patch+json) or a Map<String, Object> body.