Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Spring If-Match writes: reject a stale receipt revision

Last updated: 30 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

A conditional write compares the client's representation tag with the current revision before applying a state change.

Download Spring source kit

The stale client does not overwrite

The checked controller starts at receipt version one. PUT without If-Match gets 428 under this fixture's API policy; a stale tag gets 412. The current tag permits a write and creates version two. Reusing the old tag then fails and the stored state remains reviewed.

The code supports one quoted strong tag. Full HTTP If-Match syntax includes other forms and requires an HTTP-aware implementation. Conditional GET validates a read cache; a write precondition protects an update. Neither grants authorization.

The in-memory lock is not a database lock

The fixture synchronizes one controller object so its check and update are one local critical section. Two server instances would have independent state and locks. A persisted service needs a database compare-and-set or optimistic version check inside the write transaction. JPA optimistic locking covers one such persistence boundary.

Checked source

Java
@PutMapping(path = "/contract/revisions/{id}", consumes = "text/plain")
synchronized ResponseEntity<String> update(@PathVariable int id,
    @RequestHeader(value = "If-Match", required = false) String expected,
    @RequestBody String nextState) {
    if (id != 7) return ResponseEntity.notFound().build();
    if (expected == null) return ResponseEntity.status(428).build();
    String current = ""receipt-7-v" + version + """;
    if (!current.equals(expected)) return ResponseEntity.status(412).eTag(current).build();
    state = nextState;
    version++;
    return ResponseEntity.ok().eTag("receipt-7-v" + version).body(state);
}

Verification boundary

ReceiptIfMatchWriteTest.staleClientCannotOverwriteTheCurrentRevision runs in the downloadable Spring source kit. The excerpt omits imports and setup; the kit has complete test source.

Costs and boundaries

MockMvc tests one local controller instance and its serial state. It does not prove full HTTP tag parsing, concurrent writes across processes, persistent revision checks, authorization, or retry idempotency.

Common Mistakes

  • Do not accept a stale tag and silently overwrite current state.
  • Do not use an in-memory synchronized method as a distributed compare-and-set.
  • Do not treat an ETag as evidence that a caller owns the receipt.

Read next

Spring MVC conditional GET: validate the representation tag, Spring JPA optimistic locking: reject a stale stock update, Spring method authorization: reject a cross-tenant read.

Continue with Spring delivery contracts

Continue with Spring retry and stale-write checks: choose the right HTTP contract.

Continue with checked tenant commands

Continue with Spring JDBC versioned tenant update: inspect the affected row count.

spring
spring-boot
if-match-receipt-write
Storage details