Skip to content

feat(http): add option to retry only unprocessed mutations - #2251

Merged
Vincent Biret (baywet) merged 2 commits into
microsoft:mainfrom
jackson-asmith:fix/retry-unprocessed-mutations-only
Nov 13, 2026
Merged

Vincent Biret (baywet) merged 2 commits into
microsoft:mainfrom
jackson-asmith:fix/retry-unprocessed-mutations-only

Conversation

@jackson-asmith

@jackson-asmith Jackson Smith (jackson-asmith) commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #2250

This ports microsoft/kiota-dotnet#864 to Java. The other ports so far are microsoft/kiota-http-go#312 and microsoft/kiota-python#897.

Problem

RetryHandler retries 429, 503 and 504 responses whatever the HTTP method. For POST and PATCH, a retry after a 504 can apply the request twice. A 504 means a gateway stopped waiting, not that the server did nothing, so the first attempt may already have succeeded. With Microsoft Graph's sendMail, this sends the same email twice.

IShouldRetry already receives the request, so a caller can filter by method today. Most callers, including users of the generated SDKs, never set IShouldRetry, though, so they keep the retries that can send twice. A built-in option makes the safe behavior a single setting.

Change

This adds RetryHandlerOption.retryUnprocessedMutationsOnly(), matching the .NET option.

  • Default is false. Existing behavior doesn't change.
  • When true, POST and PATCH are retried only after a 429 or 503. By common service convention, those responses mean the server didn't process the request. A 504 is returned to the caller.
  • Other methods aren't affected. GET, PUT, DELETE and QUERY keep retrying as before.
  • The check runs on every attempt, so a POST that gets a 503 and then a 504 stops at the 504.
  • It works as a client-wide option or per request, through the request tag like the other retry settings.

RetryHandlerOption has no setters, so the setting comes through a new constructor. The existing constructors pass false.

RetryHandlerOption option = new RetryHandlerOption(
        RetryHandlerOption.DEFAULT_SHOULD_RETRY,
        RetryHandlerOption.DEFAULT_MAX_RETRIES,
        RetryHandlerOption.DEFAULT_DELAY,
        true);

KiotaClientFactory adds RedirectHandler before RetryHandler, so the retry handler only sees the request for the current redirect hop and resends exactly that. Checking request.method() is therefore always the method being resent.

Tests

RetryHandlerTest has 18 new tests, using MockWebServer like the existing ones, covering:

  • the default
  • each method and status combination above
  • method matching that ignores case
  • the 503 then 504 sequence
  • the per-request option
  • RedirectHandler and RetryHandler together: a POST that a 302 turns into a GET is retried after a 504, and a POST kept by a 307 is not

With the new check removed from the retry condition, the 5 tests for the new behavior fail and the rest pass. Locally with JDK 21, ./gradlew :components:http:okHttp:build passes, including Spotless and SpotBugs. I didn't run the Java 8 build or the Android API-level lint locally.

Add RetryHandlerOption.retryUnprocessedMutationsOnly, set through a new
constructor. It defaults to false, which keeps the current behavior.
When it is true, POST and PATCH requests are retried only after a 429
or 503 response, which indicate the server did not process the
request. A 504 is returned to the caller, because the server may
already have applied the request and a retry could apply it twice.
Other methods are unaffected.
Copilot AI balanced review requested due to automatic review settings November 13, 2026 17:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The tests still omit the POST-to-GET redirect regression, leaving retries unverified for a 302 → 504 → 200 sequence that should record POST, GET, GET.

1 open issue
What changed in this PR

Adds opt-in retry protection to Kiota’s OkHttp middleware to reduce duplicate POST/PATCH side effects after gateway timeouts, while preserving existing defaults.

Changes:

  • Adds retryUnprocessedMutationsOnly, defaulting to false, with documentation of its safety assumptions.
  • When enabled, restricts POST/PATCH retries to 429 and 503 on every attempt; other methods remain unchanged.
  • Adds tests for defaults, method/status combinations, successive responses, and per-request configuration.
File Description
components/​http/​okHttp/​src/​test/​java/​com/​microsoft/​kiota/​http/​middleware/​RetryHandlerTest.java Adds regression coverage for the opt-in retry policy.
components/​http/​okHttp/​src/​main/​java/​com/​microsoft/​kiota/​http/​middleware/​RetryHandler.java Applies method-aware eligibility to each retry.
components/​http/​okHttp/​src/​main/​java/​com/​microsoft/​kiota/​http/​middleware/​options/​RetryHandlerOption.java Exposes and documents the option while preserving constructor defaults.

Review effort: Balanced

Run RedirectHandler and RetryHandler together with the option on. A
POST that a 302 turns into a GET is retried after a 504, and a POST
kept by a 307 is not.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues are identified for the opt-in retry-protection change.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@github-project-automation github-project-automation Bot moved this to In Progress 🚧 in Kiota Nov 13, 2026
@baywet
Vincent Biret (baywet) added this pull request to the merge queue Nov 13, 2026
Merged via the queue into microsoft:main with commit 3559f2c Nov 13, 2026
9 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress 🚧 to Done ✔️ in Kiota Nov 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done ✔️

Development

Successfully merging this pull request may close these issues.

RetryHandler retries non-idempotent POST requests on 504, which can duplicate side effects such as Graph sendMail

3 participants