Hello! In our last lesson, we focused on the foundational principles of REST, designing clean URIs and using HTTP methods and status codes correctly. We ended with a crucial question: what happens when your well-designed API needs to change? Unplanned changes can break client applications, so managing this evolution is a critical skill for any senior engineer.
Today, we will tackle this head-on. Your learning outcome is to compare and contrast different API versioning strategies (e.g., URI, Header). For an interview at a FAANG-level company, simply listing the strategies isn't enough. You need to articulate the trade-offs of each, justify your choice in a given scenario, and ideally, discuss how you'd implement it in a framework like Spring Boot.
1. Why Do We Need API Versioning?
An API is a public contract. Once clients start building on it, you can't just change the response format of /api/products from returning a simple price field to a complex price object without breaking them.
When faced with a required breaking change, you have two philosophical approaches:
- Continuous Evolution: Try to avoid breaking changes altogether by only making additive changes (e.g., adding new optional fields, adding new endpoints). This is the ideal but is often not practical for significant architectural shifts.
- Explicit Versioning: Acknowledge the breaking change and release a new, numbered version of the API (v2, v3, etc.), while maintaining the old version for a period of time to allow clients to migrate. This is the focus of our lesson.
Interestingly, some REST purists, like Roy Fielding (a principal author of the REST architectural style), have argued against explicit versioning. The idea is that a well-designed API should be able to evolve seamlessly.
API Versioning In Spring Framework 7 by Rossen Stoyanchev @ Spring I/O 2025
To understand this debate and its practical implications, let's hear from Rossen Stoyanchev, a core Spring Framework developer. He provides a nuanced perspective on why, despite the theoretical ideal, versioning is often a practical necessity for real-world applications with machine clients.
Watch the segment from 02:50 to 07:32. Pay attention to the contrast he draws between human-driven browser clients and machine clients, and why 'favoring compatibility' has its limits.
As you just saw, while we should always try to make non-breaking changes, for most complex systems, especially in B2B or public API contexts, explicit versioning becomes an indispensable tool for managing change gracefully.
2. The Four Main Versioning Strategies
There are four common strategies for a client to tell your server which version of the API it wants to use. Let's start with a high-level overview.
This article from Baeldung provides a concise summary and code examples for the main versioning strategies in a Spring context. We will use it to get a quick overview of each method before diving deeper.
Read sections 3.1 through 3.4. For each of the four strategies (URI, Request Parameter, Header, and Content Negotiation), focus on understanding the example URI or header and the basic Spring implementation snippet.
Let's now analyze each of these strategies in more detail, focusing on their trade-offs—the kind of discussion required in a senior-level interview.
3. A Deep Dive into Each Strategy
A. URI Path Versioning
This is arguably the most common and straightforward strategy. The version is placed directly in the URL path.
Example:
GET /api/v1/users/123GET /api/v2/users/123
Pros:
- Explicit and Clear: The version is immediately visible to anyone looking at the URL, which is great for debugging and documentation.
- Easy to Test: You can test different versions directly in a web browser without special tools.
- Simple Routing: In Spring, you can easily map different controllers to different version paths (e.g.,
UserV1Controllermapped to/api/v1/users).
Cons:
- Violates REST Principles: A core tenet of REST is that a URI should identify a unique, stable resource. With URI versioning,
/api/v1/users/123and/api/v2/users/123point to the same underlying user, which purists argue is incorrect. - URI Clutter: It pollutes the URI space.
- Poor Caching: It can lead to challenges with HTTP caching, as caches see them as two completely different resources.
B. Header Versioning
With this strategy, the URI remains clean and stable, and the client specifies the version in an HTTP header.
Example:
- Using a custom header:
GET /api/users/123with headerX-API-Version: 2 - Using the standard
Acceptheader:GET /api/users/123with headerAccept: application/json;version=2

Pros:
- Clean URIs: The URI remains the same across versions, adhering more closely to REST principles.
- Better Caching: Since the URI is stable, HTTP caching can be more effective.
- Doesn't Pollute URI space: Your resource paths remain focused on the resource hierarchy.
Cons:
- Less Visible: The version is "hidden" in the headers, making it harder to test with a browser and less obvious when debugging from logs.
- Requires Client Awareness: Clients must remember to set the header correctly. It's less intuitive than changing a URL.
C. Content Negotiation (Media Type) Versioning
This is a more formal and standardized variation of header versioning that uses custom media types in the Accept header.
Example:
GET /api/users/123with headerAccept: application/vnd.company.app-v2+json
Pros:
- Most RESTful: This is considered the "purest" REST approach, as it uses the built-in HTTP mechanism for content negotiation to request a specific representation of a resource.
- Highly Flexible: It allows for versioning different representations (JSON, XML) of the same resource independently.
Cons:
- Complex: It's the most complex to implement and can be confusing for API consumers who are not familiar with custom media types.
- Verbose: The
Acceptheaders can become long and cumbersome.
D. Query Parameter Versioning
This approach is similar to URI versioning but uses a query parameter instead of a path segment.
Example:
GET /api/users?version=2
Pros:
- Easy to use and test: Like URI versioning, it's simple to test in a browser.
Cons:
- Adds clutter: Still clutters the URL.
- Difficult Routing: It can make routing logic within the application more complex than path-based versioning, as query parameters are typically used for filtering, not routing. This is generally considered the least favorable approach among the four.
4. Making a Strategic Choice: The Interview Perspective
In an interview, you'll be expected to choose a strategy and defend it. There's no single "best" answer; the right choice depends on the context.
API Versioning In Spring Framework 7 by Rossen Stoyanchev @ Spring I/O 2025
Let's return to Rossen Stoyanchev's talk. He offers a powerful mental model for choosing between path-based and header-based versioning, which you can use to structure your interview answers.
Watch from 08:28 to 11:48. Focus on his distinction: use path versioning for 'deep, structural changes' (treating it as a new system) and header versioning for more 'lightweight, formatting changes' to the same resource.
This distinction is key. Let's synthesize this and other factors into a comparison table. This is the kind of analysis you should be prepared to provide.
| Aspect | URI Path | Header / Content Negotiation | Query Parameter |
|---|---|---|---|
| REST Compliance | Low | High | Medium |
| Cache Efficiency | Poor | Excellent | Poor |
| Ease of Use (Client) | High (very easy) | Medium (requires header config) | High (very easy) |
| Visibility/Debuggability | High | Low (hidden in headers) | High |
| Best For | Major, breaking structural changes. When v2 is almost a "new API". | Minor, backward-incompatible formatting changes to the same resource. | Generally discouraged, but simple use cases. |
Test your understanding! (Interview Scenario)
An interviewer says: "We have a public-facing Order API. We need to introduce a breaking change: the customer field, which was a simple ID, will now become a nested object with id, name, and address. Which versioning strategy would you recommend and why?"
Show answer
A strong answer would be:
"This is a significant, structural breaking change, so we definitely need explicit versioning. The two primary candidates are URI versioning and Header versioning.
Header versioning would keep our URIs clean (/api/orders/{id}), which is good for REST principles and caching. A client would request the new format with a header like X-API-Version: 2. This is a good choice if we consider the 'Order' resource to be fundamentally the same entity, but we are just changing its representation.
However, URI versioning (/api/v2/orders/{id}) is more explicit. Given that this is a public-facing API, clarity and ease of use for our clients are paramount. A developer can see the version directly in the documentation and in the URL they are calling. It makes debugging far simpler.
My Recommendation: For a public API with a structural breaking change like this, I would recommend URI Path Versioning. The clarity and simplicity it provides for consumers outweigh the REST purism of header versioning. It makes it unmistakably clear that /api/v2/orders is a new contract. In our Spring Boot application, this also allows for very clean implementation by creating a OrderV2Controller and mapping it to the /api/v2 path, completely isolating the new logic from the old. We can then establish a clear deprecation policy for the v1 endpoints."
Conclusion
You now have a comprehensive framework for discussing API versioning. You can not only list the strategies but also compare their trade-offs and make a context-aware recommendation—a hallmark of a senior engineer.
Key Takeaways:
- Versioning is a practical necessity for managing breaking changes in APIs.
- The four main strategies are URI Path, Header, Content Negotiation, and Query Parameter.
- URI Path is best for major, structural changes due to its explicitness.
- Header/Content Negotiation is best for lightweight, representational changes and adheres more closely to REST principles.
- The choice is a trade-off between REST purity, cacheability, and developer experience (for both producers and consumers of the API).
Preview of the Next Lesson:
We've spent the last two lessons designing the "front door" of our services—their APIs. Now, we'll zoom out and look at the "floor plan" of the entire system. How do we organize these services? In our next lesson, we'll kick off the Microservices module by comparing monolithic vs. microservices architectures, analyzing their trade-offs and when to choose one over the other.