Why two versions coexist
OData v2 is the mature, widely supported protocol used by SAP Gateway and most of the classic Fiori Elements apps on ECC/S4 On-Premise. OData v4 is the OASIS standard version, leaner and more expressive, and it's the one RAP generates by default for new S/4HANA developments.
Batch requests
v2 groups several operations into a single HTTP request using $batch with a verbose multipart format. v4 simplifies this with a JSON-based batch format that is easier to read and debug in the browser network tab.
{
"requests": [
{
"id": "1",
"method": "GET",
"url": "Contracts('4500001234')"
},
{
"id": "2",
"method": "PATCH",
"url": "Contracts('4500001235')",
"body": { "ContractManager1": "DOLL" },
"headers": { "Content-Type": "application/json" }
}
]
}Deep create / deep update
v4 supports creating a header and its child items in a single request (deep insert), something v2 handled more awkwardly through navigation properties. This maps directly to RAP's composition model, where a root entity and its child entities are saved together in one transaction.
POST /Contracts { "ContractManager1": "DOLL", "_Items": [ { "Description": "Network infrastructure", "Amount": 12000 }, { "Description": "Substation maintenance", "Amount": 4800 } ] }
Draft handling
Draft (the "save your progress without committing" pattern behind every modern Fiori app) is a v4-only concept, tightly integrated with RAP's with draft clause. There is no equivalent standard mechanism in v2 — draft-enabled apps on ECC typically simulate it with custom Z-tables.
Quick comparison
- Metadata format: v2 uses XML/EDMX; v4 supports JSON metadata alongside EDMX.
- Query options: v4 adds
$compute,$searchand richer$expandwith nested filters. - Actions vs. functions: v4 formally distinguishes bound/unbound actions (side effects) from functions (read-only), matching RAP's action model.
- Error format: v4 standardises a cleaner JSON error payload, easier to parse on the SAPUI5 side.
Authentication and CSRF tokens
Both versions protect write operations (POST/PATCH/DELETE) with a CSRF token, fetched with a GET and header X-CSRF-Token: Fetch before the actual request. SAPUI5's ODataModel handles this automatically — it's worth knowing it exists for when you consume an OData service manually with fetch.
const tokenResponse = await fetch('/odata/Contracts', { method: 'GET', headers: { 'X-CSRF-Token': 'Fetch' } }); const csrfToken = tokenResponse.headers.get('X-CSRF-Token'); await fetch("/odata/Contracts('4500001234')", { method: 'PATCH', headers: { 'X-CSRF-Token': csrfToken, 'Content-Type': 'application/json' }, body: JSON.stringify({ ContractManager1: 'DOLL' }) });