PATTERN Cited by 1 source
Accept-header format negotiation for legacy sunset¶
Problem¶
When a service replaces a legacy interface, the consuming
fleet doesn't migrate in a day. At Zalando's scale the
pre-PRAPI Product pipeline had ~350 engineering teams
consuming at least three distinct legacy data formats
(alpha, beta, gamma) plus an intended new standard.
A big-bang cutover is infeasible:
- Too many consumers to coordinate.
- Some consumers have slow release cadences.
- Some consumers are downstream of other consumers (cutting over leaf consumers before their upstream is done is impossible).
The pattern the team used: one endpoint, many formats,
differentiated by Accept header.
"Engineers meticulously analyzed and replicated existing transformations within PRAPI, allowing client teams to request data in their required format via the Accept header:
application/json— New standard format for all teams;application/x.alpha-format+json— Legacy (previously on event stream);application/x.beta-format+json— Legacy (previously on event stream);application/x.gamma-format+json— Legacy (from Presentation API)."(Source: sources/2025-03-06-zalando-from-event-driven-chaos-to-a-blazingly-fast-serving-api.)
Pattern¶
- Expose a single URL per logical resource.
- Accept every legacy format as a distinct media type via
Acceptheader — usually with a vendor-prefixed name (application/x.<name>+json) so they can't collide with IANA-registered types. - Default (no
Acceptor*/*) to the new canonical format. - Internally, store only the canonical form; serialise into the legacy formats on the response path.
- Track usage per format. Every legacy format has a sunset window; usage tracking tells you who's still on it and when you can actually turn it off.
Why Accept headers over URL versioning¶
- One URL — consumers don't have to change their configs to migrate to the new format; flipping a header is enough.
- Per-request control — the same consumer can issue
different requests with different
Acceptvalues, useful during a staged per-endpoint migration. - No URL churn — URLs stay stable; only the data shape varies.
- Headers are easy to forward through proxies and gateways; URL rewriting in multi-hop paths is fragile.
Pairing with legacy-format emission¶
If the legacy format was previously delivered via an event
stream (not just HTTP), Accept-header negotiation alone
doesn't cover consumers that read from the stream. The
complementary pattern is
legacy-format emission for incremental sunset: have the
new service re-emit the legacy formats onto the legacy
streams for a fixed sunset window. Producers can
decommission immediately; consumers migrate on their
schedule.
Zalando ran both in parallel: HTTP consumers used Accept
headers; event-stream consumers continued reading
alpha/beta from the original streams, which PRAPI itself
was now emitting.
Trade-offs¶
- Transformation-maintenance cost. Every legacy format is a transformation the new service has to keep working and test. The investment is justified for migration but is debt; these transforms should be deleted at sunset.
- Schema drift risk. If the canonical model loses a field that the legacy format carries, the transform has to reconstruct it — either by keeping the field in canonical form, deriving deterministically, or returning a default. Each option has failure modes.
- Monitoring complexity. Per-format metrics must be tracked independently; p99 latency with alpha format may differ from the default if serialisation is heavier.
When this is wrong¶
- Small consumer count with good coordination — just do a big-bang cutover.
- Format differences reflect genuine semantic
differences —
Acceptheaders for behaviour-altering changes are confusing; URL versioning or explicit endpoint split may be clearer. - Legacy format is inherently incompatible. If the canonical model cannot reconstruct the legacy shape without loss, don't pretend — provide migration tooling instead.
Seen in¶
- sources/2025-03-06-zalando-from-event-driven-chaos-to-a-blazingly-fast-serving-api — PRAPI's canonical instance, paired with legacy-stream re-emission for event-stream consumers. The post ties this directly to the adoption success of the PODS program: "To ensure adoption, PRAPI took ownership of all legacy representations of Product and Offer data."
Related¶
- concepts/backward-compatibility — the discipline
- concepts/api-versioning — a sibling tactic (URL path, query param, custom header)
- concepts/legacy-format-emission-for-incremental-sunset — the event-stream complement
- patterns/compatibility-mode-client-transition
- patterns/api-as-single-source-of-truth-over-event-streams
- systems/zalando-prapi