Versioning a REST API is one of those architectural chores that everyone knows they need to do, but nobody actually wants to implement. As your data models evolve, you have to maintain backward compatibility for existing clients while serving new data structures to updated ones.
Historically, this meant writing a lot of boilerplate: duplicating entities, adding routing logic, and carefully wiring fallback behavior. The problem is not that this is hard. The problem is that it is easy to get wrong, and once it is wrong, you carry that decision for years.
If you want a deeper breakdown of the long-term consequences, the earlier article on Quarkus API Versioning Strategies goes into detail on how these decisions age over time.
What changes now is not the problem. What changes is the effort required to implement it.
As my co-author Alex Soto (Developer Advocate at IBM) demonstrated in our latest video, generative AI changes the balance. With IBM Bob, you no longer spend time writing versioning plumbing. You spend time choosing the right strategy.
And that distinction matters.
A Simple Mental Model
There are only three real ways to version an API:
Change the URL
Change the request metadata
Change the representation
Everything else is just a variation of these three.
Bob can implement all of them in seconds. But Bob does not decide which one you should use.
What is IBM Bob?
Before diving into the strategies, it helps to understand the tool.
IBM Bob is an AI coding assistant that understands your project context. It reads your existing Quarkus classes, generates new entities, updates JAX-RS endpoints, and even runs the application in dev mode to validate the result.
You are not asking for snippets. You are asking for changes to your system.
And this is where things get interesting. Bob will happily generate a second version of your API in seconds. If your strategy is wrong, you just created technical debt faster than ever.
1. Path Versioning (The Pragmatic Choice)
This is the most visible approach. The version is part of the URI:
/v1/customer
/v2/customerIn the demo, Alex asked Bob to update the Customer endpoint by renaming the name field to firstname in version two. Bob created a new CustomerV2 entity, added a new @Path("/v2/customer") endpoint, and kept the original version intact.
This is simple. It is explicit. It is easy to cache and debug.
But it also duplicates your API surface. Every version is a new endpoint, and over time you end up maintaining multiple parallel APIs.
This approach works well when you want clarity and when external consumers depend on stable URLs. It becomes harder when you have many versions active at the same time and need to keep them consistent.
If you want a deeper comparison of how this scales, the article on Quarkus API Versioning Strategies breaks this down in more detail.
2. Custom Headers (The Clean URI Approach)
Here the URI stays the same:
/bookingThe version is passed through a header:
Accept-Version: 2In the demo, Alex asked Bob to rename the customerName field to name in a Booking entity. Bob updated the resource to use @HeaderParam, routing requests to BookingV1 or BookingV2 depending on the header, and defaulting to version one if no header is present.
This keeps your URIs clean. Clients do not need to change endpoints. Versioning becomes part of the request metadata.
The problem shows up later.
After a few versions, you no longer know which clients send which headers. Removing old logic becomes guesswork. You carry old branches in your code because you are not sure who still depends on them.
This is where lifecycle management becomes critical. The article on When to Deprecate APIs explains how to handle that transition without breaking consumers.
3. Media Type / Content Negotiation (The REST Purist Way)
This approach uses the Accept header with vendor-specific media types:
application/vnd.acme.car.v1+jsonIn the demo, Alex asked Bob to rename the year field in a Car entity to date. Bob updated the @Consumes and @Produces annotations to route requests based on the media type, without changing the URI or adding custom headers.
This is the most flexible and the most aligned with REST principles. You keep a single resource and serve different representations.
But flexibility comes with complexity.
Media types are harder to debug. Clients need to construct correct headers. Tooling support is not always consistent. And once you introduce multiple representations, testing becomes more involved.
This approach makes sense in large systems where representation evolves independently from the resource. If you want to see how this works in a real integration scenario, the Quarkus Stripe API Versioning Adapter Tutorial shows how to adapt external APIs with similar patterns.
The Takeaway
The hardest part of API versioning used to be the implementation. With tools like IBM Bob, implementation takes seconds. The hard part is choosing the right strategy and managing its lifecycle over time.
Bob will implement whatever you ask for. It will do it correctly and consistently. But it will not fix a bad architectural decision.
Pick a strategy that fits your consumers, enforce it consistently, and understand what happens when you need to evolve it.
Want to try it on your own codebase? You can check out Bob at ibm.com/bob.












