Scalability & Architecture Patterns
API Versioning & Evolution
APIs must change without breaking every existing client — versioning strategy decides whether that happens through URL/header negotiation, additive-only schema evolution, or a deprecation policy with a real deadline.
- URL versioning (
/v1/orders), header versioning (Accept: application/vnd.api+json;version=2), and additive-only evolution are the three common strategies, each with different client-visibility tradeoffs - Backward-compatible ("additive") changes — new optional fields, new endpoints — do not need a version bump; removing or renaming a field, or changing its meaning, does
- A deprecation policy needs three things to be real: an announcement, a sunset date, and telemetry on who is still calling the old version
- Consumer-driven contract testing — each client publishes what it actually uses — lets a provider evolve a schema safely without knowing every client's exact code
- Running multiple API versions simultaneously multiplies operational and testing surface — every version alive is a version someone has to keep working
- Semantic versioning communicates intent, but most internal API version bumps are avoidable entirely by designing for additive evolution from the start
| URL versioning | Header versioning | Additive-only | |
|---|---|---|---|
| Client visibility | obvious in the URL | hidden in a header | no version at all |
| Caching | cache-friendly — distinct URLs | harder — same URL, different response | cache-friendly |
| When it works | any breaking change | any breaking change | only additive changes |
public record OrderResponse(
String id,
BigDecimal total,
String currency, // added later — old clients ignoring unknown fields still work
List<String> discountCodes // added later, defaults to empty — never remove a field, only add
) {}