🌐 Section 6 · Question #6

Which HTTP methods are idempotent

In the HTTP protocol specification (RFC 9110), an idempotent HTTP method is defined as a method where multiple identical requests with the same parameters produce the exact same...


🟢 Junior Level

In the HTTP protocol specification (RFC 9110), an idempotent HTTP method is defined as a method where multiple identical requests with the same parameters produce the exact same intended effect on the server’s resource state as a single request ($f(f(x)) = f(x)$).

RFC 9110 classifies standard HTTP methods into idempotent and non-idempotent categories:

Comprehensive HTTP Method Classification

HTTP Method Safe (Read-Only)? Idempotent? Semantic Intent
GET ✅ Yes ✅ Yes Retrieve resource state representation without mutations.
HEAD ✅ Yes ✅ Yes Identical to GET, but server returns headers only (no payload body).
OPTIONS ✅ Yes ✅ Yes Query supported communication options and CORS permissions.
TRACE ✅ Yes ✅ Yes Echo back received request for loopback diagnostic testing.
PUT ❌ No ✅ Yes Completely replace target resource, or create at a specific URI.
DELETE ❌ No ✅ Yes Remove target resource from the server.
POST ❌ No ❌ No Create subordinate resources, append data, or trigger business operations.
PATCH ❌ No ⚠️ Conditional Partially update a resource (RFC 5789 does not guarantee idempotency).

Intuitive Breakdown of Idempotency Guarantees

  • GET /users/42: Regardless of whether you query the endpoint 1 time or 1,000 times, the database remains completely unchanged.
  • PUT /users/42 ({"name": "Alice"}): The first invocation updates the record name to "Alice". Every subsequent execution overwrites the same record with the identical name "Alice". The resulting database state is identical.
  • DELETE /users/42: The first invocation deletes User 42 from the database. Subsequent invocations confirm that User 42 is absent. The resulting database state (User 42 does not exist) is identical after call 1 and call 10.
  • POST /orders: Submitting an order 3 times creates 3 distinct order records with 3 separate IDs and triggers 3 independent financial charges. Hence, POST is strictly non-idempotent.

🟡 Middle Level

Idempotency and Intermediate Caching (RFC 9111)

HTTP method safety and idempotency govern how intermediate caching systems (CDNs, Cloudflare, NGINX, Squid proxies) handle request routing and storage:

  1. Caching Safe & Idempotent Methods:
    • GET and HEAD responses are cacheable by default when accompanied by freshness headers (Cache-Control: max-age=3600, ETag, Last-Modified).
  2. Cache Invalidation on Modifying Methods:
    • Under RFC 9111 (HTTP Caching, Section 4.4), whenever an intermediary or origin server successfully executes a non-safe method (PUT, DELETE, POST, PATCH), it must invalidate any cached representations for:
      • The target URI (Request-URI).
      • Any URIs declared in the response’s Location or Content-Location headers.

Optimistic Concurrency Control via HTTP Preconditions (RFC 9110)

Even though PUT is idempotent, concurrent mutations from multiple clients can cause the Lost Update Problem. RFC 9110 solves this via conditional validation headers:

PUT /api/v1/products/100 HTTP/1.1
Host: api.example.com
If-Match: "v3-etag-hash"
Content-Type: application/json

{
  "price": 199.99
}
  • If the server’s current resource ETag matches "v3-etag-hash", the update succeeds and returns 200 OK (with a new ETag: "v4-etag-hash").
  • If another concurrent request altered the resource beforehand, the server aborts the mutation statelessly with 412 Precondition Failed, protecting data integrity.

Why the PATCH Specification Does Not Guarantee Idempotency

RFC 5789 intentionally designates PATCH as non-idempotent by default. Whether a PATCH request behaves idempotently depends entirely on the operation payload:

// 1. Idempotent PATCH (JSON Patch RFC 6902)
[
  { "op": "replace", "path": "/email", "value": "alice.new@example.com" }
]

// 2. Non-Idempotent PATCH (JSON Patch RFC 6902)
[
  { "op": "add", "path": "/roles/-", "value": "ADMIN" }
]
  • The replace operation is deterministic: repeating it leaves the email as "alice.new@example.com".
  • The add operation with the /- pointer appends an item to the end of an array. Repeating this request $N$ times generates an array with $N$ duplicate "ADMIN" entries (["USER", "ADMIN", "ADMIN", "ADMIN"]), altering server state on every invocation.

🔴 Senior Level

Automatic Retries in Cloud Infrastructure & Service Meshes (Istio / Envoy)

Modern cloud-native service meshes leverage HTTP method semantics to execute automated network retries without human coordination:

Envoy VirtualService Retry Routing

Because GET, HEAD, PUT, and DELETE are idempotent, proxy sidecars can automatically retry dropped requests on transient network partitions (503 Service Unavailable, connect-failure). In contrast, automated retries for POST are disabled by default:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: order-service-retry
spec:
  hosts:
  - order-service
  http:
  - match:
    - uri:
        prefix: /api/v1/orders
      method:
        exact: GET
    route:
    - destination:
        host: order-service
    retries:
      attempts: 3
      perTryTimeout: 500ms
      retryOn: "5xx,connect-failure,refused-stream"

Spring Boot 3 & Resilience4j Idempotent Retry Interceptor

When configuring microservice outbound HTTP clients (RestClient or WebClient), retries must be restricted exclusively to idempotent verbs to avoid duplicate transactional side effects:

package com.example.client;

import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.time.Duration;
import java.util.Set;

@Component
public class IdempotentRetryInterceptor implements ClientHttpRequestInterceptor {

    private static final Set<HttpMethod> IDEMPOTENT_METHODS = Set.of(
            HttpMethod.GET,
            HttpMethod.HEAD,
            HttpMethod.OPTIONS,
            HttpMethod.TRACE,
            HttpMethod.PUT,
            HttpMethod.DELETE
    );

    private final Retry retry;

    public IdempotentRetryInterceptor() {
        RetryConfig config = RetryConfig.<ClientHttpResponse>custom()
                .maxAttempts(3)
                .waitDuration(Duration.ofMillis(200))
                .retryExceptions(IOException.class) // Retry exclusively on transient network transport dropouts
                .build();
        this.retry = Retry.of("idempotentHttpRetry", config);
    }

    @Override
    public ClientHttpResponse intercept(org.springframework.http.HttpRequest request,
                                        byte[] body,
                                        ClientHttpRequestExecution execution) throws IOException {

        // Non-idempotent verbs (POST, PATCH) bypass automatic retries to protect domain integrity
        if (!IDEMPOTENT_METHODS.contains(request.getMethod())) {
            return execution.execute(request, body);
        }

        // Idempotent verbs are decorated with automated exponential retries
        try {
            return Retry.decorateCheckedSupplier(retry, () -> execution.execute(request, body)).get();
        } catch (Throwable throwable) {
            if (throwable instanceof IOException ioException) {
                throw ioException;
            }
            throw new RuntimeException("HTTP retry execution failed", throwable);
        }
    }
}

Are Incidental Side-Effects Permitted for Idempotent Methods?

Engineers often wonder if operations like audit logging or Prometheus metric recording violate the definition of idempotency.

RFC 9110 (Section 9.2.2) provides an explicit protocol clarification:

“The definition of idempotent methods does not prevent an implementation from recording each request, e.g., in an access log, or from taking actions that affect the system outside of the resource state.”

This establishes three core architectural rules:

  1. Writing a new row to an audit trail table or access.log on every GET or DELETE invocation does not violate idempotency.
  2. Incrementing a Prometheus metric counter (http_requests_total) does not violate idempotency.
  3. Idempotency is evaluated strictly and exclusively against the domain resource state managed by the target endpoint.

4 Tricky Questions

1. Is the PATCH method considered idempotent under official IETF standards?

Answer: No. According to RFC 5789, the PATCH method is non-idempotent by default.

  • While specific patch operations (such as setting an attribute to a static value via {"op": "replace"}) can be designed to produce idempotent outcomes, the specification accommodates inherently non-idempotent instructions.
  • For instance, appending an element to an array ({"op": "add", "path": "/tags/-", "value": "vip"}) or executing numeric increments modifies the resource state on every repeated invocation. Consequently, automated network retry engines treat PATCH as non-idempotent unless explicitly configured otherwise.

2. If DELETE /orders/5 returns 204 No Content on the first call and 404 Not Found on subsequent calls, does it violate HTTP idempotency?

Answer: No, it does not. Idempotency under RFC 9110 dictates that the state of the resource on the server remains identical after multiple invocations, regardless of HTTP response headers or status codes:

  • Before Request 1: Order 5 exists in the database.
  • After Request 1: Order 5 is removed (server state: absent).
  • After Request 2: Order 5 is not found (server state: absent). Because the server’s database state is identical following the 1st and the 100th invocation, the method is strictly idempotent.

3. What happens to intermediate proxy caches (CDNs, NGINX) when an idempotent mutating method like PUT or DELETE succeeds?

Answer: Under RFC 9111 Section 4.4, intermediate and origin caches must invalidate (purge) any stored representation matching the Request-URI.

  • In addition, if the response contains Location or Content-Location headers, any cached entries for those referenced URIs must also be invalidated.
  • This ensures that subsequent GET requests from other clients will not serve stale cached data following an update or deletion.

4. Does creating an audit log record or incrementing an access counter during a GET or DELETE request violate HTTP idempotency?

Answer: No. RFC 9110 Section 9.2.2 explicitly distinguishes between the primary domain resource state and incidental system telemetry:

  • The server is explicitly permitted to write access logs, audit entries, or increment analytics metrics upon receiving idempotent requests.
  • As long as the target domain resource state (e.g., the User record) is not subjected to cumulative mutations across repeated invocations, the method remains fully compliant with the specification.

🎯 Interview Cheat Sheet

30-Second Elevator Pitch

Under RFC 9110:

  • Idempotent Methods: GET, HEAD, OPTIONS, TRACE, PUT, DELETE. Repeating the request yields the exact same server resource state.
  • Non-Idempotent Methods: POST (creates new resources/executes actions), PATCH (non-idempotent by default per RFC 5789).
  • Safe $\subset$ Idempotent: All safe methods (GET, HEAD, OPTIONS) are read-only and inherently idempotent. PUT and DELETE modify data, but multiple executions stabilize to the same state.
  • Service Mesh Significance: Proxies (Envoy, Istio) and client interceptors (Resilience4j) can safely apply automated network retries only to idempotent methods.

Red Flags (What NOT to Say)

  • ❌ “All HTTP methods except POST are idempotent.” (PATCH is non-idempotent by default under RFC 5789).
  • ❌ “DELETE is non-idempotent because the first request returns 204 and the second returns 404.” (Idempotency is defined by server resource state, not response status codes).
  • ❌ “Writing a log entry to a database during a GET request violates HTTP idempotency.” (RFC 9110 §9.2.2 explicitly permits logging and telemetry side-effects).
  • ❌ “In microservices, you should configure automatic retries on all failed POST requests.” (Auto-retrying non-idempotent POST requests without Idempotency-Key leads to duplicate orders and charges).