sapui5tutors SAPUI5 • Fiori • SAP BTP Step-by-step tutorials Real project examples Interview Q&A
Practical SAPUI5 • Fiori • SAP BTP tutorials and interview prep

OData V2 vs V4: Actions, DateTime Literals and Prefer Header

Most SAP developers meet OData V4 while a V2 service is still running in production next to it. The two versions look similar at a glance — same entity sets, same query options, same $metadata — but the details differ in ways that break code silently. Here are the differences you will actually hit, in the order you will hit them.

The big picture

OData V4 isn't just a newer version — it cleaned up several V2 quirks that tripped up every SAP developer. Three of the most visible changes: function imports became actions and functions, datetime literals switched to ISO 8601, and the Prefer header gave clients control over response payloads. If you're moving from V2 (still common in older Gateway services) to V4, these are the differences you'll hit first.


Function Imports → Actions and Functions

In OData V2, custom operations were exposed as function imports — a single concept covering everything. V4 splits them into two precise concepts:

  • Functions are side-effect free. They return data and must use GET. Think of them as computed queries.
  • Actions can have side effects. They use POST and can change data.
// V2: one function import, invoked via GET
GET /sap/opu/odata/sap/ZBOOKING_SRV/ApproveBooking?BookingId='42'

// V4: a bound action, invoked via POST
POST /sap/opu/odata4/sap/zbooking/0001/Bookings('42')/com.sap.approve
Content-Type: application/json

The V4 approach is more explicit about intent, and bound actions read naturally as operations on a specific entity.


DateTime Literals: Goodbye datetime'...'

V2 wrapped datetime values in a verbose literal syntax. V4 adopts the ISO 8601 standard that every other modern API uses.

// V2 literal
$filter=CreatedAt eq datetime'2026-09-29T10:30:00'

// V4 literal — plain ISO 8601
$filter=createdAt eq 2026-09-29T10:30:00Z

No wrapper, no quotes. If your V4 queries fail with literal errors, this is usually why — old V2 habits die hard.


The Prefer Header: return=minimal

When you POST a new entity, do you want the created entity echoed back in the response, or just a confirmation? V4 lets the client decide with the Prefer header:

POST /sap/opu/odata4/sap/zbooking/0001/Bookings
Prefer: return=minimal
Content-Type: application/json

{"bookingId": "43", "customer": "1001"}
  • Without the header: the service returns 201 Created with the full entity in the body.
  • With Prefer: return=minimal: the service returns 204 No Content — just the status, no body.

Use return=minimal when you already have the data client-side and don't need the round-trip payload. It saves bandwidth, which matters on mobile and high-volume scenarios.


The JSON got leaner: goodbye d and __metadata

The first thing you notice reading a V4 response is how much less of it there is. V2 wrapped every payload in a d object and attached a __metadata block to every single entity:

// V2 response — notice the wrappers
{
  "d": {
    "__metadata": {
      "uri": ".../Bookings('42')",
      "type": "ZBOOKING_SRV.Booking"
    },
    "BookingId": "42",
    "Customer": "1001"
  }
}

V4 drops both. The payload is just the data, with a small @odata.context URL at the top describing the shape:

// V4 response — just the data
{
  "@odata.context": "$metadata#Bookings/$entity",
  "bookingId": "42",
  "customer": "1001"
}

This matters more than it looks. Client code written against V2 almost always navigates through response.d.results — every one of those paths breaks on V4, where the array sits directly in value (or the entity sits at the root). It is a mechanical fix, but it touches every read in the codebase, which is why migration estimates that only count "changed endpoints" come in low.

If your V4 migration estimate only counts changed endpoints, it is missing the biggest line item: every client read path that navigates through the old d wrapper needs rewriting.

What stayed the same (reassuringly)

Not everything changed. The core query language — $filter, $orderby, $top, $skip, $expand, $select — works the same way, with the same operators. Key addressing in URLs (Bookings('42')) is unchanged. And $metadata still describes the model, so tools and client generators keep working with minor adjustments.

The mental model carries over too: entity sets, entity types, navigation properties, and the idea that the URL addresses the data model. V4 refined the protocol; it did not redesign the concepts. A developer fluent in V2 query writing is productive in V4 within a day — the learning curve is in the payload details, not the querying.


A practical migration checklist

When you sit down to move a service or a client from V2 to V4, work through these in order:

  1. Client read paths: replace d.results / d navigation with V4's value / root-entity shape.
  2. Count handling: $inlinecount=allpages becomes $count=true, and the __count property becomes @odata.count.
  3. String search: substringof('x', prop) eq true becomes contains(prop, 'x') — arguments flip.
  4. Datetime literals: drop the datetime'...' wrapper, use plain ISO 8601.
  5. Custom operations: split function imports into GET functions (read-only) and POST actions (side effects).
  6. Write methods: replace MERGE with PATCH; check every PUT for accidental full-replacement semantics.
  7. Create responses: decide per call whether you want the entity echoed back or Prefer: return=minimal for a lean 204.

Run the old and new services side by side during the transition and diff the payloads for a few representative entities. The structural differences are predictable; the bugs come from the one read path or literal format nobody remembered to check.


Quick Reference

  • V2 function imports → V4 actions (POST, side effects) and functions (GET, read-only).
  • V2 datetime'...' → V4 plain ISO 8601.
  • Prefer: return=minimal → 204 No Content instead of 201 with the entity.

Key takeaway: OData V4 didn't just renumber the version — it replaced V2's function imports with explicit actions/functions, adopted ISO 8601 datetimes, and gave clients payload control via the Prefer header. Learn these three and V4 starts feeling familiar fast.


Bottom line

V4 is V2 with the sharp edges removed: leaner JSON, explicit actions versus functions, standard datetime literals, proper PATCH semantics, and client-controlled response payloads. The query language you already know carries over untouched. Work through the migration checklist methodically — read paths, counts, string functions, literals, operations — and the transition is a series of small mechanical changes rather than one big rewrite.