Customizing Microsoft Graph SDK Middleware for Resilient Calls
Configure Microsoft Graph SDK middleware to handle retries, timeouts, and telemetry consistently before production load exposes default gaps.
27 Sept 2026, 06:31 UTC

The production problem defaults do not solve
You ship a Microsoft 365 integration that calls Microsoft Graph. It works fine in development with a handful of test users, then begins returning HTTP 429 throttling responses or timing out once the workload reaches production scale. The common reaction is to wrap each call in a retry loop. That usually makes throttling worse: every retry adds another request to the same constrained endpoint, and naive loops often retry errors that will never succeed, such as 403 Forbidden.
The useful takeaway is to configure the SDK’s middleware pipeline once at application startup instead of decorating individual requests. The pipeline is the supported place to inject retry behavior, timeout overrides, logging, and tenant-aware token selection.
How the SDK middleware pipeline works
All supported Microsoft Graph SDKs—.NET, JavaScript, Java, Go, and PowerShell—run outgoing requests through a middleware pipeline. In .NET these components are called handlers; in JavaScript, Java, and Go they are middleware. Each component executes in registration order on the outbound trip and in reverse order on the response trip. A logger registered first sees the request before any other component and sees the response after all others.
This ordering matters. If you add a retry handler after a logging handler, the logger records every attempt, including retries. If you add it before, the logger may see only the final outcome. Choose the order deliberately based on what you need to observe.
Retry defaults: what is retried and what is not
Default retry policies typically cover HTTP 429, 503, and 504 with exponential backoff and jitter, and they honor the Retry-After header when the service sends it. Exact defaults vary by SDK language and major version, so do not assume the behavior you saw in one SDK version carries forward.
Defaults do not retry 401 or 403 responses. Token refresh is handled by the authentication provider, such as MSAL, before the request enters the pipeline. Custom middleware must not attempt to refresh tokens or reissue requests on 401/403; doing so creates race conditions where multiple components try to acquire a new token simultaneously.
Timeouts, batching, and upload sessions
Timeout configuration is not uniform across SDKs. The .NET SDK relies on HttpClient, which defaults to 100 seconds but can be overridden per request. The JavaScript SDK has no global default and uses AbortController for cancellation. The Java SDK defaults to a 10-second call timeout unless customized. Long-running operations, such as creating an upload session for a large file, use dedicated SDK classes that manage chunking and retries outside the standard middleware pipeline.
Graph batching is another exception. The batch request itself passes through the pipeline, but individual sub-requests may not invoke per-request middleware in some SDK implementations. Apply retry and idempotency logic at the batch level, or avoid batching for writes that must succeed exactly once.
Worked example: adding request logging middleware
The following .NET example shows the shape of a custom middleware component. API names reflect Microsoft Graph .NET SDK 5.x patterns; verify the exact interface against your installed version before copying it into production.
using Microsoft.Kiota.Abstractions;
// ...
public class RequestLoggingMiddleware : IGraphRequestMiddleware
{
private readonly ILogger<RequestLoggingMiddleware> _logger;
public RequestLoggingMiddleware(ILogger<RequestLoggingMiddleware> logger)
{
_logger = logger;
}
public async Task<HttpResponseMessage> InvokeAsync(
HttpRequestMessage request,
RequestOption[] options,
Func<HttpRequestMessage, RequestOption[], Task<HttpResponseMessage>> next,
CancellationToken cancellationToken)
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
_logger.LogInformation("Graph request {Method} {Url}", request.Method, request.RequestUri);
var response = await next(request, options, cancellationToken);
stopwatch.Stop();
_logger.LogInformation("Graph response {StatusCode} in {ElapsedMs}ms", response.StatusCode, stopwatch.ElapsedMilliseconds);
return response;
}
}
Register the middleware in your application startup, before you create the Graph service client. Where to run: in your application project, typically Program.cs or Startup.cs. Required permissions: none beyond normal application build permissions. Expected checks: application logs should show one request line and one response line per Graph call, with increasing elapsed times if retries occur. Relevant risk: never log the Authorization header or request body containing customer data; scrub sensitive headers before writing to logs.
Limitations and pre-flight checks
- SDK major versions break middleware APIs. .NET moved from DelegatingHandler to IGraphRequestMiddleware between 4.x and 5.x; JavaScript changed from a middleware array to a pipeline builder between v4 and v5.
- The PowerShell Microsoft.Graph SDK has limited middleware extensibility. For advanced resilience, call the REST API directly with Invoke-RestMethod or use the .NET SDK from PowerShell.
- National cloud deployments require different service roots and authority hosts. Switch both together, or requests fail with 400 or 401.
- Default retry behavior does not apply to Graph batching sub-requests in all SDKs.
Before deploying, verify with a minimal console app: add a middleware that logs request URL, attempt number, and elapsed time. Run against a test tenant with Microsoft Graph Developer Proxy configured to return 429 twice, then 200. Confirm the retry count matches your SDK default and that backoff intervals increase. Also test token expiry mid-flight to confirm MSAL refreshes silently without custom middleware intervention.
Closing
Do not discover your retry strategy during a production throttling event. Register middleware at startup, confirm the default retry and timeout values for your SDK version, and treat batching and upload sessions as exceptions to the standard pipeline. A few minutes of pipeline configuration now prevents cascading failures later.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.