Backward-Compatible API Versioning in Production
How to evolve REST and gRPC APIs without breaking mobile clients and partner integrations.
You cannot force every mobile user to update the app on the day you deploy. Backward-compatible API evolution is how backend teams ship weekly without breaking clients in the wild.
Rules that work
- Additive changes are safe — New optional fields, new endpoints, new enum values (if clients ignore unknowns).
- Breaking changes need a new version —
/v2/...or new package in gRPC. - Never repurpose fields — Changing
status: 1from “pending” to “cancelled” breaks everyone. - Deprecate in phases — Mark old fields deprecated in docs, log usage, remove only when traffic is zero.
Versioning styles
| Style | Example | Notes |
|-------|---------|--------|
| URL path | /api/v1/orders | Clear, easy to route at gateway |
| Header | Accept-Version: 2 | Keeps URLs clean |
| gRPC package | order.v2.OrderService | Strong contracts with protobuf |
Pick one per product and stick to it.
Expand and contract (database)
- Expand: Add new column nullable or with default.
- Deploy code that writes both old and new shapes.
- Migrate data if needed.
- Contract: Stop writing old fields; remove later.
Same idea applies to event schemas in Kafka.
Testing compatibility
- Contract tests against golden JSON fixtures from oldest supported app version.
- Canary deploy with shadow traffic comparison.
Real-world impact
On payment and super-app backends, backward compatibility plus zero-downtime deploys meant we could run multiple app versions against one API surface for months.
Takeaway
Treat your public API as a long-lived contract. Version when you must; evolve additively when you can. Your future self (and mobile team) will thank you.