Microsoft Graph Batching: Optimizing Bulk Operations vs. Individual Requests
Learn when to use Microsoft Graph $batch to bundle up to 20 requests, how to handle partial failures, and the critical trade-offs between batching and individual API calls.
21 Aug 2025, 14:12 UTC

The bulk-operation bottleneck
When you need to update 100 SharePoint list items, create 50 Teams channels, or modify user properties for a department, sending individual HTTP requests creates a performance bottleneck. Each request incurs network round-trip latency and consumes your application's request quota. The Microsoft Graph $batch endpoint allows you to bundle multiple requests into a single call, but it is not a transactional wrapper. Understanding how batching interacts with throttling and error handling is critical to avoiding data inconsistency.
How JSON Batching Works
A batch request is a POST to https://graph.microsoft.com/v1.0/$batch. The payload is a JSON object containing a requests array. Each entry requires a unique id (used to correlate the response), a method, and a url.
Crucially, the service returns a responses array. Even if the outer HTTP call returns a 200 OK, individual sub-requests within that batch may have failed with 400, 403, or 429 status codes. Your application must iterate through the response array and map the id of each result back to the original request to determine success.
Worked Example: Creating Multiple Teams Channels
To create three channels in one team, you can bundle the requests. This example assumes you have an Entra ID app with Channel.Create permissions. Run this via Graph Explorer or a client using a delegated token.
POST https://graph.microsoft.com/v1.0/$batch
Content-Type: application/json
{
"requests": [
{
"id": "chan-1",
"method": "POST",
"url": "/teams/{team-id}/channels",
"body": {
"displayName": "Project Alpha",
"description": "Alpha workstream"
}
},
{
"id": "chan-2",
"method": "POST",
"url": "/teams/{team-id}/channels",
"body": {
"displayName": "Project Beta",
"description": "Beta workstream"
}
},
{
"id": "chan-3",
"method": "POST",
"url": "/teams/{team-id}/channels",
"body": {
"displayName": "Project Gamma",
"description": "Gamma workstream"
}
}
]
}
If "Project Alpha" already exists, the response for id: "chan-1" will return a 400 Bad Request, while chan-2 and chan-3 may return 201 Created. This partial success is the default behavior of the batch endpoint.
Engineering Trade-offs and Limitations
- No Atomic Transactions: There is no automatic rollback. If the fifth request in a batch of ten fails, the first four remain committed. You must implement your own compensation logic to delete or revert changes if a batch must be "all or nothing."
- Request Limits: Microsoft Graph typically limits batches to 20 requests. Attempting to send more may result in a
400error before any sub-request is processed. For larger sets, chunk your data into groups of 20. - Throttling (429): While batching reduces the number of HTTP connections, the backend still processes the underlying operations. Heavy write operations in a batch can still trigger throttling. When a
429 Too Many Requestsoccurs, check theRetry-Afterheader to determine the wait period. - Payload Size: There is a maximum payload limit (often cited around 4 MB). Large binary data, such as uploading a file to OneDrive, cannot be batched and must use the dedicated upload session APIs.
- Dependencies: While some Graph operations support a
dependsOnproperty to enforce order within a batch, this is not universal across all endpoints. For complex sequences (e.g., Create Group → Create Team → Add Member), sequential individual requests are often more reliable.
Decision Matrix: Batch vs. Individual
| Scenario | Recommended Pattern | Reasoning |
|---|---|---|
| Updating 100 user profiles (PATCH) | Batch (Chunks of 20) | Reduces latency; idempotent updates are safe to retry. |
| Creating a resource and then using its ID in the next call | Sequential Individual | Avoids complex dependsOn logic and ensures ID availability. |
Fetching data via @odata.nextLink |
Individual Requests | Pagination and Delta queries are not supported inside batch payloads. |
| High-volume, low-latency read operations | Parallel Individual | May provide better throughput if the app has a high concurrency limit. |
Verification Steps
- Test Partial Failure: Send a batch where one request is intentionally malformed (e.g., an invalid URL). Verify that the other requests in the batch still succeed.
- Validate Correlation: Ensure your code maps the
idfrom the response array back to the original request object rather than relying on the array index. - Check Throttling: Execute several large batches in rapid succession and monitor for
429responses to determine your tenant's specific throughput ceiling.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.