skip to content

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%

answer

  1. the clients that send no ceiling
  2. prefix-free payloads
  3. lower-case, dollar-prefixed options
  4. 4.01 must still speak 4.0

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.

solid answer

~40 s

OData 4.01 is designed as a compatible increment: its minimal conformance level includes 4.0's, a conforming JSON producer must still produce 4.0 payloads for a 4.0 request, and it must accept both 4.0 and 4.01 URL syntax. The exposure is clients that send no `OData-MaxVersion`. 4.01 says a service SHOULD keep answering them at the version it launched with, but its conformance list also lets it return 4.01 content unless the client sends `OData-MaxVersion: 4.0`. Such a client might then see `@context` instead of `@odata.context`, `@type` without `#`, decimals in exponential notation, `@removed` in deltas, and a `$metadata` with `Version="4.01"`. Interoperable clients MUST send `OData-MaxVersion`, use lower-case `$`-prefixed query options, and keep the type prefix on duration and enumeration literals. Shared caches need `Vary: OData-MaxVersion`.

go deeper

for a junior

Recall that 4.01 is an incremental OASIS release over 4.0 and that OData-MaxVersion is how a client caps the version it receives.

for a middle

Explain the payload differences a 4.0 parser could trip on: unprefixed control information, @type without #, exponential decimals and @removed in deltas.

for a senior

Show you would audit which clients omit OData-MaxVersion, add Vary for caches, and replay real 4.0 traffic before flipping the service to 4.01.

for a principal

Decide which 4.01 capabilities justify the upgrade, such as schema versioning, JSON batch or $compute, against the cost of chasing every silent 4.0 client.

## The promise 4.01 makes OData 4.01 (OASIS Standard, 23 April 2020) is an incremental release over 4.0 (2014). The committee's non-normative "What's New" note says a compliant 4.01 service fully supports 4.0 clients. The normative texts back that up: - The 4.01 **minimal conformance level** begins with "MUST conform to the OData 4.0 Minimal Conformance Level". - A conforming JSON producer **MUST** support generating OData 4.0 payloads when the `OData-Version` is 4.0: keeping the `odata.` prefix, keeping `#` on `@odata.type` values, and not writing exponential decimals unless asked. - The service **MUST** support both 4.0 and 4.01 syntax in URLs, whatever `OData-MaxVersion` the client sent. - It **MUST** accept both prefixed and unprefixed forms of headers and preferences, such as `OData-Isolation` and `Isolation`. - Its `$metadata` **MUST** say `Version="4.0"` when the request carried `OData-MaxVersion: 4.0`. A 4.0 client that states its ceiling is therefore safe by construction. ## Where 4.0 clients can still break The gap is the client that sends **no** `OData-MaxVersion`. 1. Under 4.0's text, a service SHOULD treat a missing ceiling as its own maximum. A 4.0 service answered 4.0; after the upgrade the same rule would yield 4.01. 2. 4.01 rewrote the rule: the service SHOULD keep returning the same version over time, treating a missing ceiling as the maximum it supported **at initial publication**, which is 4.0 for an upgraded service. 3. Its conformance list still says the service **MAY** return 4.01 behaviour, content and payloads if the client does not send `OData-MaxVersion: 4.0`. A silent 4.0 client is protected by a SHOULD, not a MUST. If the service, or a shared cache without `Vary: OData-MaxVersion`, hands it 4.01 output, these differences can break a strict parser: | 4.01 output | What a 4.0 parser expects | |---|---| | `@context`, `@count`, `@nextLink` without the prefix | `@odata.context` and friends | | `@type` value `Int64` without `#` | `#Int64` | | Decimals in exponential notation | plain decimal notation | | Deleted entities marked `@removed` in a delta | 4.0's deleted-entity shape | | `$metadata` with `Version="4.01"` and new constructs such as key-less entity types or `Edm.Untyped` | 4.0 CSDL only | | A final async response returned unwrapped, with an `AsyncResult` header | an `application/http` wrapper | ## What the upgrade adds None of this is forced on old clients, but it explains why 4.01 output differs: - **Query language**: `$compute`, the `in` operator, `divby`, `matchesPattern`, `case`, `hassubset`, and `$search` on any collection. - **Syntax**: system query options without `$` and in any case, unprefixed duration and enumeration literals, key-as-segment URLs, default namespaces. - **Data modification**: deep update, set-based PATCH and DELETE through `/$each` and `/$filter(...)`, the `omit-values` preference. - **Batch and metadata**: the JSON batch format and CSDL in JSON. - **Model evolution**: schema versioning with `Core.SchemaVersion` and `$schemaversion`, so a breaking model change no longer needs a new service root. ## Finding the silent clients first Before switching the service to 4.01, measure who is exposed. Log, per client identity, whether requests carry `OData-MaxVersion` and with what value; any client sending none is relying on the SHOULD above. Advertise the new capability in `$metadata` with the `Core.ODataVersions` annotation (a space-separated list in which 4.01 implies 4.0), and tell client owners the date from which unversioned requests may see 4.01 output. Then fix the clients, not the service: one header per request is cheaper than holding the whole service at 4.0. ## Keeping clients working: a checklist 1. Every client sends `OData-MaxVersion`; the 4.01 interoperability rules say clients **MUST**. A client written for 4.0 sends `4.0`. 2. Clients use lower-case names with the `$` prefix (`$filter`, `$orderby`) and lower-case lambda operators, because 4.0 services do not accept the 4.01 relaxations. 3. Clients keep the type prefix on duration and enumeration literals (`duration'P1D'`, `Sales.Color'Red'`), which both versions accept. 4. Parsers branch on the response's `OData-Version` instead of guessing. 5. The service sends `Vary: OData-MaxVersion` whenever responses differ by version, as 4.01 requires. 6. Contract tests replay 4.0 client traffic with and without the header before and after the upgrade. (OData 4.02 exists only as committee drafts, not as a standard, so nothing above depends on it.)

  • Does OData 4.01 let a service make a breaking model change without a new service root?
    Yes, if it versions its schema. 4.0 required a new service root for breaking changes. In 4.01 a service may annotate its metadata with `Core.SchemaVersion` and must then honour the `$schemaversion` query option. 4.01 minimal conformance requires it to reject an incompatible `$schemaversion`. This versions the model, separately from the protocol version in `OData-Version`.
  • Why must a client that targets both 4.0 and 4.01 services keep writing `$filter` rather than `filter`?
    Only 4.01 services must accept system query options without the `$` prefix and in any case. To a 4.0 service, `filter=...` is not a system query option, so the filter would not be applied as intended. The 4.01 texts say clients that want to work with 4.0 services MUST use lower-case names and the `$` prefix.

saying these in an interview costs you the question

  • 4.01 is a breaking major release, so 4.0 clients need their own service root.
  • A 4.0 client can skip OData-MaxVersion because 4.01 services must always answer in 4.0.
  • Dropping the $ from system query options works against every 4.x service.
  • Sending OData-Version: 4.0 on a GET decides which version the response uses.
  • A 4.01 service may answer a 4.0-capped client with unprefixed @context.