Why this matters
Backward compatibility means existing clients keep working as your API evolves. As an API Engineer, you will:
- Ship new features without breaking mobile apps that update slowly.
- Refactor or migrate services without mass client rewrites.
- Run safe A/B rollouts, deprecations, and version transitions.
- Avoid incidents caused by subtle contract breaks (e.g., renaming fields or changing error codes).
Real tasks you will face
- Add new optional fields while keeping old clients stable.
- Change pagination from page/per_page to cursor-based without breaking SDKs.
- Deprecate an endpoint and route traffic to a new service with a sunset policy.
- Adjust enums or validation logic without crashing older clients.
Concept explained simply
Backward compatibility means: if a client built yesterday keeps sending the same requests today, it should still get usable responses and not crash. You can add features, but existing behaviors must remain valid.
Mental model
Think of your API as a contract with readers who are strict and easily confused. If you add new words (fields), be sure they can ignore them. If you change meanings or remove words, you will confuse them. Prefer additive, opt-in change over destructive, mandatory change.
Core principles and rules
- Additive changes are usually safe: add optional fields, new endpoints, new enum values clients can ignore.
- Never remove or rename publicly used fields, paths, or headers without a migration plan.
- Preserve behavior: keep response shapes, data types, error codes, and success semantics stable.
- Be tolerant in what you accept, strict in what you produce (clients should ignore unknown fields; servers should keep outputs stable).
- Use explicit versioning for breaking changes (e.g., /v2 or header negotiation). Do not silently break /v1.
- Deprecate with a sunset policy: announce, provide an alternative, offer overlap time, and measure adoption.
- Provide defaults for new fields so old clients don’t crash on missing data.
- Document change policy: what counts as breaking, notice periods, and SLAs.
Language/protocol tips
- JSON/REST: Adding a new response field is usually safe; changing types or required fields is not.
- gRPC/Protobuf: Never reuse field numbers; use reserved tags/names when removing. Adding new optional fields is backward compatible.
- GraphQL: Deprecate fields; avoid removing until usage is near-zero. Adding fields is usually safe.
- HTTP: Keep status codes and error body format stable. New 4xx codes can break clients if they hard-check exact codes.
Worked examples
Example 1: Adding a field safely (JSON)
Before:
GET /users/42 -> {"id":42,"name":"Ava","email":"a@x.io"}
After (safe additive):
GET /users/42 -> {"id":42,"name":"Ava","email":"a@x.io","avatar_url":null}
Why safe: old clients ignore unknown fields; "avatar_url" defaults to null.
Example 2: Changing pagination strategy
Before:
GET /orders?page=2&per_page=50
Response: {"page":2,"per_page":50,"total":120,"items":[...]}
Goal: Cursor-based pagination.
Safe rollout:
- Keep page/per_page working.
- Add optional cursor parameters: GET /orders?after=abc123&limit=50
- Add response fields when cursor is used:
{"items":[...],"page":null,"per_page":null,"next_cursor":"def456"}
- Document both; monitor usage; later deprecate page/per_page with notice.
Example 3: Enum evolution
Before (order_status): "NEW","PAID","SHIPPED"
Need to add "CANCELLED".
Safe:
- Add new value and ensure clients gracefully handle unknown enums, e.g., treat as "OTHER".
- Keep server behavior consistent; do not repurpose existing values.
Unsafe:
- Renaming "PAID" to "SETTLED" (breaks strict client checks).
Example 4: Protobuf field changes
message User {
int64 id = 1;
string name = 2;
}
Need to remove name? Do not reuse tag 2.
message User {
int64 id = 1;
// reserved 2;
string display_name = 3;
}
Why: reusing tag 2 could cause data corruption with older binaries.
Decision guide: Is my change safe?
- If you are adding optional data with stable defaults → Usually safe.
- If you are changing a type (int → string), removing a field, or modifying semantics → Breaking; version or provide a migration path.
- If you rely on clients handling unknown fields → Verify via contract tests and real traffic sampling.
- If your SDK hard-codes enums/error codes → Treat adding new enums/codes as potentially breaking.
Compatibility checklist (quick)
- Stable request/response shapes.
- Unknown fields ignored by clients.
- No type changes or renames.
- Error format and codes stable.
- Idempotency unchanged.
- Deprecation timeline communicated.
- Monitoring in place during rollout.
Step-by-step rollout plan
- Design: choose additive-first approach; confirm default values; define metrics to watch.
- Contract: update OpenAPI/IDL; mark deprecated elements; add examples.
- Implement: behind a flag; keep old behavior as default.
- Test: run consumer-driven and golden-file tests; check tolerance to unknown fields.
- Shadow/Canary: ship to small percentage; monitor errors and client behavior.
- Announce: provide changelog, timelines, and migration guide; include sunset headers if deprecating.
- Gradually increase traffic; maintain roll-back plan.
- Deprecate only when usage is near-zero and risk is acceptable.
Exercises
Note: Everyone can take exercises; only logged-in users will have progress saved.
Exercise 1 — Add a field and migrate pagination without breaking clients
Design a safe change to add a new optional user field and introduce cursor pagination while keeping page/per_page working.
- Provide updated request/response examples.
- Define defaults and error behavior.
- Explain rollout, monitoring, and deprecation policy.
Input format you can follow
1) GET /users/{id} response before/after
2) GET /orders listing before/after
3) Headers (e.g., Sunset, Deprecation) you would include
4) Metrics to monitor
Exercise 2 — Spot the breaking changes
Given an old and new schema, identify breaking changes and propose fixes.
Schemas
Old JSON (User):
{
"id": 42,
"name": "Ava",
"email": "a@x.io"
}
New JSON (User):
{
"user_id": 42,
"name": {"first": "Ava", "last": "Cole"},
"email": "a@x.io",
"status": "ACTIVE"
}
- List each breaking change.
- Suggest a backward-compatible alternative.
Submission checklist
- Examples include both old and new calls.
- No field renames without aliasing.
- Defaults and nullability described.
- Monitoring and rollback covered.
Common mistakes and how to self-check
- Renaming fields instead of aliasing. Self-check: can old clients still parse the response?
- Changing types (int to string). Self-check: will strict validators reject it?
- Removing error codes or changing meanings. Self-check: do clients rely on specific codes?
- Silent behavior changes (e.g., sorting). Self-check: did you document and A/B test?
- Skipping monitoring. Self-check: which metrics confirm clients are fine?
- Reusing Protobuf field numbers. Self-check: did you mark removed fields as reserved?
Practical projects
- Blue/green API rollout: run two versions (/v1 and /v2) behind a router; gradually shift traffic and measure error rates.
- Contract test harness: create consumer-driven tests that ensure unknown fields are ignored and error formats are stable.
- Deprecation dashboard: expose metrics for usage of deprecated fields and endpoints; define automatic alerts.
Learning path
- Start: Understand additive vs. breaking changes and error stability.
- Next: Practice versioning strategies (URI vs. header) and deprecation policies.
- Advance: Implement consumer-driven contracts and canary deployments.
- Master: Run large migrations (pagination, ID formats) with zero downtime.
Who this is for
- API Engineers and Backend Developers evolving public or internal APIs.
- Platform and Reliability Engineers responsible for safe rollouts.
Prerequisites
- HTTP/REST basics or familiarity with gRPC/GraphQL.
- JSON or Protobuf schema authoring.
- Basic observability (logs, metrics, alerts).
Next steps
- Complete the exercises and take the quick test below.
- Apply one principle this week to an API you own.
- Create or refine your team’s deprecation policy.
Mini challenge
Your API must start returning a new field that can be null for new records but is missing for old records. In one paragraph, describe how you will make old clients safe, how you will document it, and what you will monitor in the first 48 hours of rollout.
Self-check (tick before you ship)
- I verified old clients still parse responses without errors.
- I did not change field types or meanings.
- I added fields instead of renaming/removing.
- I documented defaults, nullability, and deprecations.
- I have metrics and alerts for errors, status codes, and client timeouts.
- I have a rollback plan.
Reminder: The quick test is available to everyone. Only logged-in users will have saved progress.