skip to content

Version Lineage and Migration

OData v2's verbose JSON, associations and DataServiceVersion header gave way in v4 to lean JSON and OData-Version negotiation. Interviewers ask because many estates, SAP's above all, stay on v2.

part ofAPI stylesoverview, primer and where to startread it →
on this pageshow

questions

5

Handed an unfamiliar OData service, how can you tell from its responses whether it speaks OData v2 or OData v4?

level: juniorimportance: should knowfreq 14%

answer

  1. two lines, two header names
  2. the outer JSON wrapper
  3. where the collection array sits
  4. edmx root: namespace and Version

basics

~20 s

Read the version header and the JSON envelope. OData v2 sends DataServiceVersion and wraps JSON in a "d" object with __metadata on each entry; OData v4 sends OData-Version (4.0 or 4.01) and puts a collection in a top-level "value" array.

solid answer

~40 s

The fastest signal is the response header: the Microsoft-published v2 line uses `DataServiceVersion` (for example `DataServiceVersion: 2.0`), while a v4 service MUST send `OData-Version: 4.0` or `4.01`. The JSON body confirms it: v2's verbose JSON wraps every response in `{"d": ...}`, puts a collection under `results`, and gives each entry a `__metadata` object with `uri` and `type`; v4 JSON is a plain object whose collection is the `value` array, with control information such as `@odata.context` (`@context` in 4.01 payloads). The metadata document settles any doubt: a v4 `edmx:Edmx` root lives in the `http://docs.oasis-open.org/odata/ns/edmx` namespace and MUST carry `Version="4.0"` or `"4.01"`, while v2 metadata uses Microsoft's `schemas.microsoft.com/ado/...` namespaces. It matters because a v2 client cannot read v4 URLs or payloads unchanged.

go deeper

for a junior

Recall the three tells: DataServiceVersion versus OData-Version, the "d" wrapper versus a "value" array, and the OASIS edmx namespace with its Version attribute.

for a middle

Explain which tells are mandatory: OData-Version MUST appear on v4 responses, while v2's header is only encouraged, so payload shape and $metadata are the reliable checks.

for a senior

Show you check the protocol line before choosing a client generator or parser, and that you know a v2 server may answer in 1.0 shape when nothing newer is needed.

for a principal

Frame version identification as the first step of any OData integration estimate, since a v2 estate changes the URL, payload and tooling assumptions of the whole plan.

## Two lines that share one name **OData** (the Open Data Protocol) is a set of conventions for exposing a queryable data model over HTTP. It exists in two lineages that are **not wire-compatible**: | Line | Published by | Status | Version headers | |---|---|---|---| | v1 and v2 (v3 followed them) | Microsoft, as open specifications on odata.org | superseded, still running in many large enterprise estates | `DataServiceVersion`, `MaxDataServiceVersion` | | 4.0 | OASIS, OASIS Standard of 24 February 2014 (errata through 2016) | widely deployed | `OData-Version`, `OData-MaxVersion` | | 4.01 | OASIS, OASIS Standard of 23 April 2020 | the current standard | same as 4.0 | Because URLs such as `Products(1)` and options such as `$filter` look alike in both, people assume one client can talk to either. It cannot: the payload envelope, several URL forms and the version handshake all changed. So the first job with an unknown service is to find out which line it speaks. ## Signal 1: the version header - **v2**: the overview specification defines `DataServiceVersion` on requests and responses and `MaxDataServiceVersion` on requests. Sending them is only *highly encouraged*, and a response `DataServiceVersion` *should* be present, so a v2 response without one is still legal. - **v4**: the protocol says services **MUST** include `OData-Version` on every response, with the value `4.0` or `4.01`. A response with `OData-Version` is v4; one with `DataServiceVersion` is the older line. A missing header is therefore a weak hint of the older line, never proof. ## Signal 2: the JSON envelope The two lines shape the same data very differently. A v2 collection response: ```json { "d": { "results": [ { "__metadata": { "uri": "https://api.example.com/svc/Parts(7)", "type": "Acme.Part" }, "ID": 7, "Name": "Hex bolt", "Supplier": { "__deferred": { "uri": "https://api.example.com/svc/Parts(7)/Supplier" } } } ], "__count": "1" } } ``` The same data from a v4 service with the default minimal metadata: ```json { "@odata.context": "https://api.example.com/svc/$metadata#Parts", "@odata.count": 1, "value": [ { "ID": 7, "Name": "Hex bolt" } ] } ``` The markers to look for: - **`d`** as the single outer member: v2 (and v1). The v2 JSON format adds it on purpose, so a response is valid JSON but not an executable JavaScript statement. - **`results`**, **`__metadata`**, **`__deferred`**, **`__count`**, **`__next`**: v2 verbose JSON. - **`value`** plus names starting with **`@`**: v4. In a 4.0 payload they carry the `odata.` prefix (`@odata.context`); a 4.01 payload SHOULD drop it (`@context`). One trap: a v2 server answers with the *lowest* version that can serve the request, and a 1.0 response puts the entries straight into `"d": [ ... ]` without `results`. Seeing `d` is enough to place it on the old line. ## Signal 3: the metadata document Both lines publish `$metadata`, but the documents differ: 1. A v4 document's root is `edmx:Edmx` in the namespace `http://docs.oasis-open.org/odata/ns/edmx`, and it **MUST** contain a `Version` attribute of `4.0` or `4.01`. 2. Its model elements use `http://docs.oasis-open.org/odata/ns/edm`. 3. Older documents use Microsoft namespaces, for example `http://schemas.microsoft.com/ado/2008/09/edm` for CSDL 2.0, and v2 metadata carries a `DataServiceVersion` attribute so a consumer can check whether it understands the constructs. ## Signals that do not discriminate - Keys in parentheses (`Parts(7)`) exist in both lines. - `$filter`, `$orderby`, `$top`, `$skip`, `$expand`, `$select` and `$format` exist in both. - The `/$count` path segment returns a bare number in both. - Atom XML existed in both lines, though 4.01 never updated the Atom format. ## A probe sequence 1. `GET` the service root and one entity set with `Accept: application/json`, and read the response headers: `OData-Version` settles it as v4, and its value tells 4.0 from 4.01. 2. Look at the body: `d` means the old line, `value` with `@`-names means v4. 3. `GET $metadata` and read the root element's namespace and `Version`. A v4 service SHOULD also advertise its supported versions through the `Core.ODataVersions` annotation on its entity container, a space-separated list in which 4.01 implies 4.0. ## Why it matters Choosing the wrong client generation, parser or query syntax produces errors that look like data bugs: an empty list because the code read `value` from a `d` envelope, or a 400 because a v4 filter function was sent to a v2 service. Check the header, the envelope and `$metadata` before writing a line of integration code, and record the answer in the integration's documentation so the next team does not repeat the probe.

  • Why does an OData v2 JSON response wrap everything in a "d" object?
    The v2 JSON format says it is a security measure: wrapping every response in an object whose single member is `d` makes the payload valid JSON but not a valid JavaScript statement, so a page cannot execute it as a script in a cross-site attack. Request payloads are not wrapped. OData v4 JSON has no `d` member: a collection response is an object whose entries sit in `value`.
  • A response carries neither DataServiceVersion nor OData-Version. What can you conclude?
    Little from the header alone. v2 only says servers should send `DataServiceVersion`, so its absence is legal on the old line, while a v4 service MUST send `OData-Version`, so a v4 service omitting it is non-conformant. Fall back on the payload envelope (`d` versus `value`) and on the namespace and `Version` attribute of `$metadata`.

saying these in an interview costs you the question

  • OData v4 is v2 with a few extra query options, so the same client works.
  • A $metadata endpoint or keys like Parts(7) prove a service is OData v4.
  • The "d" wrapper is how OData v4 marks a data payload.
  • OData 4.01 renamed its version header when it dropped the OData- prefix.
  • A v2 response without DataServiceVersion is malformed and must be rejected.
open as a page

How do an OData client and service agree on a protocol version, and how did v2's DataServiceVersion headers differ from v4's OData-Version and OData-MaxVersion?

level: middleimportance: should knowfreq 11%

basics

~20 s

Both lines pair a version header with a ceiling. In v2 a server answers with the lowest version that serves the request, capped by MaxDataServiceVersion; in v4 the service MUST answer with the greatest version it supports that does not exceed OData-MaxVersion.

open as a page

You must move an OData v2 service and its existing clients to OData v4; what breaks on the wire, and how would you stage the migration?

level: seniorimportance: should knowfreq 8%

basics

~20 s

Almost every client-visible surface breaks: payloads, query syntax, relationship URLs, the MERGE method, the model's associations and service operations. Run a v4 service at a new root beside v2, migrate clients one by one, and retire v2 once its traffic is gone.

open as a page

What must an OData client's JSON parser change when a service moves from OData v2 verbose JSON to OData v4 JSON?

level: middleimportance: nice to knowfreq 9%

basics

~20 s

Nearly every structural assumption. OData v4 drops the "d"/"results" envelope and __metadata, reports counts and paging as @odata.count and @odata.nextLink, sends Int64 and Decimal as JSON numbers by default, writes dates as ISO 8601-style strings, and omits computable links.

open as a page

When an OData 4.0 service is upgraded to OData 4.01, which existing 4.0 clients could break, and what keeps them working?

level: seniorimportance: nice to knowfreq 5%

basics

~20 s

The 4.0 clients at risk are those that send no OData-MaxVersion, since a 4.01 service may answer them with 4.01 payloads. Clients stay safe by sending OData-MaxVersion: 4.0, which obliges the service to answer in 4.0.

open as a page