You shipped v1 of your API. Users integrated it. Now you need breaking changes. How do you evolve without breaking everyone?
API versioning seems simple until you actually do it. Here’s what works, what doesn’t, and how to pick the right strategy.
The Core Problem
APIs are contracts. When you change the response format, rename fields, or alter behavior, you break that contract. Clients built against v1 stop working when you ship v2.
The goal: evolve your API while giving clients time to migrate.
Versioning Strategies
URL Path Versioning
Pros:
- Obvious and explicit
- Easy to route in any framework
- Easy to deprecate (just shut down the old path)
- Cacheable — different URLs, different cache entries
Cons:
- Pollutes your URL space
- Can lead to massive code duplication
When to use: Public APIs, when you expect major version bumps, when simplicity matters more than purity.
Header Versioning
Or custom header:
Pros:
- Clean URLs
- RESTful purists approve
- Can version at granular level
Cons:
- Easy to forget the header
- Harder to test in browser
- Some proxies strip custom headers
When to use: Internal APIs, when URL aesthetics matter, when you have sophisticated clients.
Query Parameter Versioning
Pros:
- Easy to add to any request
- Works in browser
- Optional — can default to latest
Cons:
- Feels hacky
- Query params should filter data, not change structure
- Caching can be tricky
When to use: Honestly, rarely. It works, but other approaches are cleaner.
The Practical Approach: URL + Additive Changes
Most teams land here:
- Major versions in URL (
/v1/,/v2/) - Minor changes are additive (new fields, new endpoints)
- Never remove or rename fields within a version
| |
Clients that don’t expect email will ignore it. No breakage.
What Counts as Breaking?
Breaking changes (require new version):
- Removing a field
- Renaming a field
- Changing a field’s type (
"count": "5"→"count": 5) - Changing required/optional status
- Changing error response format
- Changing authentication method
- Removing an endpoint
Non-breaking changes (safe within version):
- Adding new fields to responses
- Adding new optional query parameters
- Adding new endpoints
- Adding new enum values (usually)
- Performance improvements
Implementation Patterns
Versioned Controllers
Keep versions completely separate:
| |
Simple but leads to duplication. Use when versions are substantially different.
Shared Logic, Version-Specific Serialization
| |
Business logic stays DRY, only serialization differs.
Feature Flags Over Versions
For internal APIs, consider feature flags instead:
| |
Roll out changes gradually, per-client. No hard version cutover.
Deprecation Done Right
Don’t just kill old versions. Give clients a migration path:
1. Announce Early
Add headers to every v1 response, months before shutdown.
2. Provide Migration Guides
Document every change:
| |
3. Sunset Gradually
4. Monitor Usage
Track which clients still use deprecated endpoints:
| |
What About GraphQL?
GraphQL handles this differently — no explicit versions. Instead:
- Add new fields anytime
- Mark old fields
@deprecated - Eventually remove (after checking usage)
| |
Clients see deprecation warnings in tooling. Works well for sophisticated clients, harder for simple integrations.
Common Mistakes
1. Too Many Versions
Don’t version every change. If you’re at v17, something went wrong. Aim for major versions lasting 1-2 years.
2. No Default Version
Always specify what happens with no version:
| |
Or return an error requiring explicit version — stricter but clearer.
3. Breaking Changes in Minor Updates
If clients expect stability within a version, deliver it. “We added a field that might be null” can still break parsers.
4. Forgetting Documentation
Your API docs need version selectors. Every example needs version context. Nothing worse than docs that don’t match the version you’re using.
The Bottom Line
Start simple:
- URL path versioning (
/v1/,/v2/) - Additive changes within versions
- Clear deprecation timeline
- Migration documentation
You can get fancy later. Most APIs never need more than this.
Your API is a promise. Version carefully, deprecate gracefully, and your clients will thank you.