There’s nothing quite like the sinking feeling when you realize your shiny new API change just broke half of your clients in production. Been there. More than once. If you haven’t yet, you will. Let’s talk honestly about how to handle breaking API changes before your users light up your inbox.
Why Do Breaking Changes Even Happen?
No matter how much you plan, business requirements shift. Data models evolve. That UserDTO you designed in 2018 is now missing fields, has fields you regret, and uses camelCase for reasons no one remembers. Sometimes, you just have to break things.
But there’s a world of difference between breaking things thoughtfully and unleashing chaos. Let’s get into the real options.
The Usual Suspects: Versioning Approaches
I’ve wrestled with all of these in C#/.NET (Web API, Clean Architecture), Python (FastAPI), and when integrating with frontend teams using React.
1. URI Versioning
Example: /api/v1/orders, /api/v2/orders
Pros:
- Easy to implement and discover
- Multiple versions can run side-by-side
Cons:
- Clients might ignore newer versions
- You end up supporting
/v1forever if you’re not careful
What Actually Happened:
We kept /v1 alive “just for a few months.” Two years later, it was still there, with a frightening amount of traffic. Sunsetting old versions is a project, not a toggle.
2. Header Versioning
Example:
GET /orders with header Api-Version: 2
Pros:
- Keeps URLs clean
- Lets you version at a granular level (per resource)
Cons:
- Harder for humans to debug
- Some proxies and tools strip custom headers (found this out the hard way)
Production Pain:
One team’s Postman collection didn’t set the header, so they thought the API was just broken. It’s amazing how often invisible mechanisms get missed.
3. Query Parameter Versioning
Example: /orders?version=2
Pros:
- Simple to test and roll out
- Visible to clients
Cons:
- Feels a bit hacky (though sometimes that’s fine)
- Can make caching and routing trickier
Where This Bit Us:
A misconfigured CDN cached responses without considering the version parameter. Suddenly, v2 clients were getting v1 data. Debugging that was… not fun.
The Real Trade-Offs
Let’s be honest, none of these are perfect. Here’s what I’ve learned:
You will have to support multiple versions for a while. Plan for it. Don’t assume everyone upgrades on day one.
Communicate breaking changes early and loudly. Internal docs, changelogs, and even Slack reminders. Assume nobody reads them, then remind them again.
Automate testing across all supported versions. CI that only checks v2? That’s a future incident waiting to happen.
Set a realistic sunset policy. But be ready to negotiate with that one critical partner who never migrates.
Code Example: Minimal Versioning in ASP.NET Core
Here’s a stripped-down approach I actually shipped (and later refactored). It’s not perfect, but it’s honest:
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/orders")]
public class OrdersController : ControllerBase
{
[HttpGet]
public IActionResult Get() { /* ... */ }
}
You can combine this with Microsoft.AspNetCore.Mvc.Versioning for more flexibility. Just remember, adding versioning later is way harder than starting with it.
What About the Frontend?
React devs hate surprises. We broke the contract once by removing a field. The result? Frontend blew up, users saw blank pages, and nobody was happy.
Solution? OpenAPI specs as contracts.
Generate types automatically for React using tools like openapi-typescript
Run contract tests in CI to catch breaking changes before deploy
When to Make a Breaking Change (and When Not To)
Here’s my (hard-earned) rule of thumb:
Only break when you must. If you can deprecate a field or add new ones without breaking, do that.
Batch breaking changes. If you’re going to make a v2, make it count. Don’t version every tiny tweak.
Give clients a migration path. Feature flags, dual writes, or accept both old and new formats for a while.
What we can DO TODAY
Pick a versioning strategy now, not later. Even if you think you won’t need it, you probably will.
Automate backward compatibility tests. Don’t trust yourself to remember all the edge cases.
Document and communicate. Docs are great, but Slack reminders and real conversations prevent surprises.
Sunset old versions ruthlessly. But expect pushback.
If you’ve survived a gnarly API versioning incident (or you strongly disagree with my approach), I genuinely want to hear your story. What’s the worst versioning pain you’ve dealt with? Or, if you’ve found a strategy that actually made your life easier, drop it in the comments.
Top comments (0)