What is OData's $batch request for, and why does a valid one return 200 OK even when a request inside it fails?
answer
- one POST, many requests
- service root plus $batch
- outer status versus inner status
- 200 means accepted, not succeeded
basics
~20 sOData's $batch packs many requests into one POST to the service root's $batch URL to save round trips. Its outer 200 OK only says the batch headers were valid and processing began; each inner request reports its own status.
solid answer
~50 sA `$batch` request is a single `POST` to `<service root>/$batch` whose body carries many individual requests (reads, writes, action and function calls) as `multipart/mixed` parts (OData 4.0 onward) or, from OData 4.01, as a JSON `requests` array. Each inner request is evaluated with the same semantics it would have outside the batch. The protocol says that if the batch's own headers are valid, the service MUST return `200 OK`: the batch was accepted for processing, which lets the service stream results, and inner requests may still fail. Only an invalid batch envelope earns a `4xx`, and then nothing is processed. So a client must read every inner status (the status line of each `application/http` part, or the `status` member of each JSON response object matched by `id`) and never treat the outer `200` as success.
go deeper
Recall that $batch is one POST to the service root's $batch URL, and that its outer 200 OK means accepted, not succeeded.
Explain the two status layers, the batch envelope versus each inner response, and how a client matches inner responses to its requests in each format.
Show error handling built on per-request outcomes, including requests that never ran, with credentials on the outer request and preconditions on the inner ones.
Weigh one batched round trip against an opaque outer 200: monitoring, alerting and retries must look inside the batch to see what actually failed.
## What a $batch request is An OData service exposes a **batch endpoint** at the URL `$batch` relative to its service root. A client sends one HTTP `POST` to that URL, and the body carries many **individual requests**. The OData 4.01 protocol lists what an individual request may be: a metadata request, a data request, a data modification request, an action invocation or a function invocation. Each one is evaluated with exactly the same semantics it would have if it were sent on its own; a batch changes the transport, not the meaning. The point is fewer round trips. A screen that needs a customer, that customer's open orders and the product list would otherwise make three calls; a batch makes one. The body comes in one of two formats: - **Multipart format** (OData 4.0 and later): the batch request carries `Content-Type: multipart/mixed` with a `boundary` parameter, and each individual request is a body part with `Content-Type: application/http`. - **JSON format** (added in OData 4.01): the batch request carries `Content-Type: application/json`, and the body is an object whose `requests` array holds one object per request. - **Response format**: a batch request SHOULD carry an `Accept` header naming `multipart/mixed` or `application/json`; without one, the service SHOULD answer in the request's own content type. ## Two layers of status A batch response has two kinds of status, and confusing them is the classic mistake. | Layer | What it reports | Where the client reads it | |---|---|---| | The outer response to the `POST` | whether the batch request itself was acceptable | the HTTP status line of the batch response | | Each inner response | the outcome of one individual request | the status line inside each `application/http` part, or the `status` member of each JSON response object | The protocol is explicit: if the batch request's set of headers is valid, the service **MUST** return `200 OK`, meaning the batch was accepted for processing while processing is not yet complete. Inner requests may still fail or turn out to be malformed. The stated reason is **streaming**: the service can start writing results before it knows how later requests will end, so the outer status cannot summarise them. If the batch's own headers are invalid, the service MUST return a `4xx` and process nothing. OData does not use `207 Multi-Status` here. Every inner response is a complete HTTP response, formatted exactly as it would appear outside a batch, carried inside the `200 OK`. ## What a client has to do 1. **Check the outer status first.** A `4xx` means nothing ran; fix the batch envelope (its `Content-Type`, boundary or other headers). 2. **Walk every inner response.** In the multipart format the response mirrors the request's structure, and parts inside a change set carry the `Content-ID` of their request. In the JSON format, response objects may arrive in any order, and each carries the `id` of its request. 3. **Handle each failure on its own terms.** An inner `404`, `412` or `400` carries a standard OData error body, exactly as it would outside a batch. 4. **Notice requests that never ran.** In the multipart format, processing stops at the first error unless the client asked to continue, so missing parts mean not executed, not failed. ## What belongs on the outer request and what on the inner - **Credentials** go on the outer `POST`: a multipart body part representing a single request MUST NOT include authentication or authorization headers, nor `Expect`, `From`, `Max-Forwards`, `Range` or `TE`. - **Preconditions** go on the inner requests: `If-Match` and `If-None-Match` MUST NOT be specified on the batch request, but MAY be specified on individual requests. - **Version**: a batch SHOULD carry the applicable `OData-Version` header, and inner requests without one inherit it. ## Version notes - **OData v2** answered a valid batch with `202 Accepted`. **OData v4** changed this to `200 OK`; in v4, `202 Accepted` is reserved for asynchronous processing that the client requested with the `respond-async` preference. - **OData 4.0** defines only the multipart format; **OData 4.01** adds the JSON format. ## Common misreadings - Reading `200 OK` as "all succeeded" and skipping the inner statuses. - Expecting an inner `404` or `500` to surface as the outer status. - Putting an `Authorization` header in each multipart part instead of on the batch. - Matching JSON batch responses to requests by array position instead of by `id`.
- Why may a request part inside an OData multipart $batch not carry its own Authorization header?The protocol says a body part representing a single request MUST NOT include authentication or authorization headers, nor `Expect`, `From`, `Max-Forwards`, `Range` or `TE`. Credentials belong on the outer `POST` to `$batch`. A client that needs different credentials for different requests sends separate batches.
- If an OData client omits the Accept header on a $batch request, which format does the response use?A batch request SHOULD carry an `Accept` header naming `multipart/mixed` or `application/json`. Without one, the service SHOULD answer in the request's own content type: a multipart batch gets a multipart response, and a 4.01 JSON batch gets a JSON response.
Handing a courier a sealed box of letters: the courier's signature says the box was accepted for delivery, not that every recipient said yes. Each letter's answer comes back on its own.
saying these in an interview costs you the question
- A 200 OK on an OData $batch response means every inner request succeeded.
- OData reports a partly failed batch with 207 Multi-Status.
- One failed inner request turns the whole batch response into a 500.
- Each request part in a multipart batch should carry its own Authorization header.
- Requests inside a batch follow looser, batch-only semantics than standalone requests.