What are the best practices for API versioning in microservices?
Version an API only when a change would break existing clients. Pick one versioning style and use it everywhere. Put the version in the URI path if you want the simplest option. Keep old versions running until clients have moved. Everything else follows from those three rules.
The hard part in microservices is not the version number. It is that dozens of services ship on their own schedule, and each one has clients it cannot force to upgrade.
The four versioning styles compared
| Style | Looks like | Strengths | Weaknesses |
|---|---|---|---|
| URI path | /v1/orders | Visible, easy to route, easy to debug in a log | Version leaks into every URL, clients edit paths to upgrade |
| Custom header | X-API-Version: 1 | URLs stay clean, easy to default | Invisible in browsers and logs, easy to forget |
| Query parameter | /orders?version=1 | Simple to add and remove | Clutters caching keys, often ignored by proxies |
| Accept header | Accept: application/vnd.acme.v1+json | Correct use of content negotiation | Hardest to test, needs real negotiation logic |
Most teams pick URI path versioning. It is explicit, and a gateway can route on it without parsing headers. An engineer reading an access log can see which version a client used.
Version only on breaking changes
A breaking change is one that makes a working client stop working. Adding a new optional field is not breaking. Removing a field is.
Non breaking, no new version needed:
- Adding a new endpoint.
- Adding an optional request field with a default.
- Adding a new field to a response, if clients ignore unknown fields.
- Adding a new value to an enum, if clients handle unknown values.
Breaking, needs a new version:
- Removing or renaming a field.
- Changing a field type, such as a string that becomes an object.
- Making an optional request field required.
- Changing the meaning of an existing value.
- Changing an error code that clients branch on.
If you version on every release, you will run five versions within a year. Version on breaking changes only, and most services stay on v1 for a long time.
Semantic versioning, and where it fits
Semantic versioning writes a version as MAJOR.MINOR.PATCH, for example 2.1.3. A major bump means a breaking change. A minor bump adds features in a compatible way. A patch fixes a bug without changing the contract.
Use the full three part number for the service artifact and its client libraries. Expose only the major number in the API path, so the URL is /v2/orders and not /v2.1.3/orders. Clients should not have to change their code because you released a patch.
Keep a tolerant reader on the client side
The tolerant reader pattern means a client ignores anything in a response it does not recognise. It reads the fields it needs and leaves the rest alone.
This one habit removes most of the pressure to version. If every consumer tolerates unknown fields, producers can add fields freely and never cut a new version for it.
Enforce it in code review, and test it. Send a response with an extra field to a consumer's test suite and confirm nothing breaks.
Publish a deprecation policy before you need one
A version you cannot retire is a version you maintain forever. Decide the rules up front and publish them.
A workable policy has four parts:
- A support window. State how long an old major version keeps working after its replacement ships. Six to twelve months is common for external APIs.
- A warning in the response. Send a
Deprecationheader and aSunsetheader with the retirement date. Clients then see it in traffic, not only in an email. - Usage tracking. Log calls per version per client. You cannot retire a version safely without knowing who is still on it.
- A named contact path. Tell clients how to ask for an extension, and how to report a migration blocker.
Internal APIs can move faster than external ones, because you can find every caller. Say which of the two an API is, and hold each to a different bar.
Let the gateway carry the routing
An API gateway sits in front of your services and routes requests. Give it the version routing job.
The gateway reads /v1/ or the version header, then sends the call to the right service instance or the right handler. The service behind it stays focused on one thing. Rolling out v2 becomes a routing change, not a rewrite of every caller.
The gateway is also the right place to add deprecation headers and count usage per version. It can shed traffic from a retired version with a clear error rather than a timeout.
Version services, not the whole platform
In a microservices system, do not bump a global platform version. Version each service's public contract on its own.
If the payments service makes a breaking change, only payments goes to v2. Orders, shipping, and search stay where they are. A shared version number forces every team to move together, which is the coupling that microservices exist to avoid.
Common mistakes
- Versioning on every deploy, which produces versions nobody asked for.
- Running so many live versions that a bug fix has to land in five places.
- Shipping
v2and retiringv1on the same day. - Putting a version in the path but never routing on it, so
v1andv2hit the same code. - Silent breaking changes, where the field is gone but the version did not move.
- No usage data, so nobody knows whether a version is safe to retire.
Frequently asked questions
What is the best API versioning strategy for microservices?
URI path versioning with a major number only, for example /v1/orders. It is visible, a gateway can route on it, and it shows up in logs. Combine it with tolerant readers on the client side so most changes need no new version at all.
When should I create a new API version?
Only when a change would break a working client. Removing a field, renaming one, changing a type, or making an optional field required are all breaking. Adding optional fields and new endpoints is not.
How many API versions should I support at once?
Two is a healthy target: the current one and the previous one. Every extra live version multiplies testing and bug fixing. A published support window is what keeps the number down.
Should each microservice have its own version?
Yes. Version each service's contract on its own so teams can ship independently. A single platform wide version forces every team to move together, which defeats the point of separate services.
What is the difference between URI versioning and header versioning?
URI versioning puts the version in the path, so it is visible to anyone reading a URL or a log. Header versioning keeps URLs clean but hides the version, which makes debugging and caching harder. Both work. Consistency matters more than the choice.
How do I retire an old API version safely?
Announce the retirement date, send Deprecation and Sunset headers on every response, track usage per client, and contact the clients still calling it. Retire only when usage reaches zero or the window closes.
How to prepare
Treat versioning as part of the contract. Interviewers ask how you would add a field without breaking anyone, which is a versioning question in disguise. Grokking Modern API Design Interview has a lesson on versioning and backward compatibility and applies it across 15 worked designs.
Know the microservices context too. Grokking Microservices Design Patterns covers how gateways, contracts, and independent deploys fit together.
Related reading

GET YOUR FREE
Coding Questions Catalog

$123

$197

$72