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

StyleLooks likeStrengthsWeaknesses
URI path/v1/ordersVisible, easy to route, easy to debug in a logVersion leaks into every URL, clients edit paths to upgrade
Custom headerX-API-Version: 1URLs stay clean, easy to defaultInvisible in browsers and logs, easy to forget
Query parameter/orders?version=1Simple to add and removeClutters caching keys, often ignored by proxies
Accept headerAccept: application/vnd.acme.v1+jsonCorrect use of content negotiationHardest 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:

  1. 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.
  2. A warning in the response. Send a Deprecation header and a Sunset header with the retirement date. Clients then see it in traffic, not only in an email.
  3. Usage tracking. Log calls per version per client. You cannot retire a version safely without knowing who is still on it.
  4. 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 v2 and retiring v1 on the same day.
  • Putting a version in the path but never routing on it, so v1 and v2 hit 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.

TAGS
Microservice
System Design Interview
CONTRIBUTOR
Arslan Ahmad
Arslan Ahmad
ex-FAANG engineering manager and author or Grokking series.
-

GET YOUR FREE

Coding Questions Catalog

Design Gurus Newsletter - Latest from our Blog
Boost your coding skills with our essential coding questions catalog.
Take a step towards a better tech career now!
Explore Answers
What is Apple's HR strategy?
What is the Stripe layoff package?
What are the basic steps of system design?
What is the fresher package in Atlassian?
Why do I want to work at Palantir?
What do software interns do?
Related Courses
New
Grokking the AI System Design Interview course cover
Grokking the AI System Design Interview
Learn to design AI systems the way interviewers expect: classic ML products, LLM and RAG architectures, and agentic systems, all through the lens of the system design interview.
4.6
(3,192 learners)
Discounted price for Your Region

$123

Grokking the Coding Interview: Patterns for Coding Questions course cover
Grokking the Coding Interview: Patterns for Coding Questions
The 24 essential patterns behind every coding interview question. Available in Java, Python, JavaScript, C++, C#, and Go. The most comprehensive coding interview course with 543 lessons. A smarter alternative to grinding LeetCode.
4.6
Discounted price for Your Region

$197

Grokking Modern AI Fundamentals course cover
Grokking Modern AI Fundamentals
Master the fundamentals of AI today to lead the tech revolution of tomorrow.
4.1
Discounted price for Your Region

$72

Design Gurus logo
One-Stop Portal For Tech Interviews.
Copyright © 2026 Design Gurus, LLC. All rights reserved.