<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Prem Kapadne</title>
    <description>The latest articles on DEV Community by Prem Kapadne (@prem09_27).</description>
    <link>https://dev.to/prem09_27</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3992615%2F2fa659e1-8d80-49be-8dfc-766442f04279.jpg</url>
      <title>DEV Community: Prem Kapadne</title>
      <link>https://dev.to/prem09_27</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/prem09_27"/>
    <language>en</language>
    <item>
      <title>The Version That Broke Everything</title>
      <dc:creator>Prem Kapadne</dc:creator>
      <pubDate>Fri, 19 Jun 2026 17:15:52 +0000</pubDate>
      <link>https://dev.to/prem09_27/the-version-that-broke-everything-31ej</link>
      <guid>https://dev.to/prem09_27/the-version-that-broke-everything-31ej</guid>
      <description>&lt;p&gt;&lt;em&gt;A deep dive into API versioning — what goes wrong without it, the old way developers survived it, and why Spring 7's new approach changes everything.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;⏱ 12 min read · 🎯 Intermediate · 🛠 Spring Boot 3.x / Spring 7+&lt;/p&gt;




&lt;h2&gt;
  
  
  It was a Tuesday. Nothing seemed wrong.
&lt;/h2&gt;

&lt;p&gt;Imagine a dev team at a fast-growing startup. They have a food delivery platform — FoodRush — serving over a million mobile users, 5,000 restaurant partners, and 50 third-party business integrations. The backend is humming. The original restaurant menu API has been rock-solid for two years:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET https://api.foodrush.com/restaurants/menu/{restaurant_id}

// Response looked like this — clean, simple
{
  "restaurant": "Pizza Palace",
  "items": [
    {
      "id": 101,
      "name": "Margherita Pizza",
      "price": 12.99,
      "available": true
    }
  ]
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the product team had "improvements." They wanted richer data — restaurant ratings, nested pricing with currency info, prep times. A well-meaning developer updated the API. One deployment. No flags. No versioning. The new response looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;💥 BREAKING CHANGE

{
  "restaurant": {           // was a string, now an object
    "name": "Pizza Palace",
    "id": "rest_001",
    "rating": 4.5
  },
  "menu_items": [            // "items" renamed to "menu_items"
    {
      "id": 101,
      "name": "Margherita Pizza",
      "pricing": {            // flat "price" replaced with nested object
        "amount": 12.99,
        "currency": "USD"
      },
      "availability": {       // boolean → object
        "in_stock": true,
        "prep_time": 20
      }
    }
  ]
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The next morning:&lt;/strong&gt; Slack explodes. The iOS app is crashing on launch. The restaurant partner dashboard is showing blank menus. Three enterprise integrations are throwing 500 errors. Support tickets are flooding in. The on-call engineer stares at the screen. The API "works" — it returns 200. But every single client is broken, because they were all parsing &lt;code&gt;response.items&lt;/code&gt; and &lt;code&gt;response.restaurant&lt;/code&gt; as a string. Now those fields don't exist in the old shape anymore.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Four breaking changes. Zero warning. Every client — mobile app, website, restaurant apps, delivery partner apps, third-party integrations — broke simultaneously. What should have been a simple product improvement turned into a company-wide incident. And it was entirely preventable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚡ &lt;strong&gt;The Real Problem&lt;/strong&gt;&lt;br&gt;
Evolving an API is inevitable. Products grow, requirements change, better designs emerge. The sin isn't changing your API — it's changing it without giving your clients a safe path through the transition.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What API versioning actually means
&lt;/h2&gt;

&lt;p&gt;API versioning is not complicated in concept. It's the practice of allowing multiple versions of your API to exist simultaneously, so old clients keep working while new clients adopt the improved contract. Think of it like a software release: you don't delete version 1 the moment version 2 ships. Both live side by side until every client has migrated.&lt;/p&gt;

&lt;p&gt;In the FoodRush disaster, the fix would have been simple. Instead of silently replacing the response shape, they should have done this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;// v1 — still returns the original format. Zero disruption.
GET https://api.foodrush.com/v1/restaurants/menu/{restaurant_id}

// v2 — new format for clients ready to upgrade
GET https://api.foodrush.com/v2/restaurants/menu/{restaurant_id}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this in place, the migration becomes gradual and controlled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Week 1 — Launch v2:&lt;/strong&gt; v1 still works with zero disruption. v2 is available with the new format. Email sent to all developer partners about v2.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Month 1 — Mobile migration:&lt;/strong&gt; New app version uses v2. Old app versions still work on v1. Users update at their own pace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Month 2 — Partner migration:&lt;/strong&gt; Partners test v2 in staging, switch when ready. No forced deadlines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Month 6 — Deprecation notice:&lt;/strong&gt; "v1 will be deprecated in 6 months." 99% of traffic on v2. Stragglers helped to migrate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Year 1 — Sunset v1:&lt;/strong&gt; Turn off v1 with confidence. All clients migrated. Zero business disruption.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the power of versioning: you control the pace of change. Clients don't get surprised. Your API evolves without breaking the trust of people building on top of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The four ways to version an API
&lt;/h2&gt;

&lt;p&gt;Before we look at code, it's worth understanding the conceptual approaches. Every versioning strategy is just answering one question differently: &lt;strong&gt;how does the client tell the server which version it wants?&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🛣️ URI Path&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/v1/users&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔍 Query Param&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/users?version=1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;📋 Header&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X-API-VERSION: 1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🎭 Media Type&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Accept: application/vnd.myapp.v1+json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last one — Media Type Versioning — is the most REST-compliant but also the least understood. Quick explainer: MIME stands for Multipurpose Internet Mail Extensions. It's the standard way to describe what type of data is being sent. The string &lt;code&gt;application/vnd.myapp.v1+json&lt;/code&gt; breaks down as:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;application&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Top-level MIME type — general category of data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vnd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Vendor-specific — this is a custom type, not a global standard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;myapp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your application or company name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The API version you're requesting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;+json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The data format — return it as JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;vnd&lt;/code&gt; prefix signals that this media type belongs to you — it's specific to your application, not a global internet standard. GitHub, for example, uses &lt;code&gt;application/vnd.github.v3+json&lt;/code&gt;. The URL stays clean and stable; only the representation changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The old way — and why it hurts
&lt;/h2&gt;

&lt;p&gt;Before Spring 7, developers had to wire up versioning manually inside their controllers. The mapping annotations — &lt;code&gt;@GetMapping&lt;/code&gt;, &lt;code&gt;@PostMapping&lt;/code&gt; etc. — carried the full versioning burden. Let's look at all four approaches in the legacy style:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. URI path versioning (legacy)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;({&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/v1"&lt;/span&gt;&lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;defaultPathVersion&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Response from API 1.0.0"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/v2"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;pathV2Version&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Response from API 2.0.0"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Request parameter versioning (legacy)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"version=1"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;defaultReqParamVersion&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Response from API 1.0.0"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"version=2"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;v2ReqParamVersion&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Response from API 2.0.0"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Request header versioning (legacy)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"X-API-VERSION=1"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;defaultReqHeaderVersion&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Response from API 1.0.0"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"X-API-VERSION=2"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;v2ReqHeaderVersion&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Response from API 2.0.0"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Media type versioning (legacy)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;produces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"application/vnd.eazyapp.v1+json"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;defaultMediaTypeVersion&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Response from API 1.0.0"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;produces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"application/vnd.eazyapp.v2+json"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;v2MediaTypeVersion&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Response from API 2.0.0"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance these look fine. But imagine you have 50 endpoints across 10 controllers. Now imagine you need to add v3. Here's what the pain feels like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;😵 &lt;strong&gt;Version logic lives in the wrong place.&lt;/strong&gt; Your controller's job is to handle business logic — not to know which HTTP header a client passes. Versioning concerns bleed into every single method.&lt;/li&gt;
&lt;li&gt;🔁 &lt;strong&gt;Maintenance overhead explodes.&lt;/strong&gt; Adding a new version means touching every controller, every method. In a large codebase that's hundreds of changes — all error-prone, all manual.&lt;/li&gt;
&lt;li&gt;🤯 &lt;strong&gt;No central source of truth.&lt;/strong&gt; Want to know which versions your API supports? You have to grep through every annotation in every controller. There's no single configuration file that tells you.&lt;/li&gt;
&lt;li&gt;📦 &lt;strong&gt;Version sprawl becomes unmanageable.&lt;/strong&gt; Three versions in, your controllers look like an archaeological dig — layers of &lt;code&gt;v1&lt;/code&gt;, &lt;code&gt;v2&lt;/code&gt;, &lt;code&gt;v3&lt;/code&gt; methods stacked on top of each other with no clear separation.&lt;/li&gt;
&lt;li&gt;🔀 &lt;strong&gt;Switching strategies is painful.&lt;/strong&gt; If you started with URI versioning and later decided header versioning is cleaner, you'd need to rewrite annotations across your entire codebase.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;The hidden cost&lt;/strong&gt;&lt;br&gt;
The legacy approach technically works. But it doesn't &lt;em&gt;scale&lt;/em&gt;. Every version you add multiplies the maintenance burden. By version 4, your codebase has become a versioning museum rather than a product.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Spring 7 way — one config to rule them all
&lt;/h2&gt;

&lt;p&gt;Spring 7 introduced a first-class API versioning system that flips the entire model. Instead of embedding version logic inside annotations scattered across your controllers, you declare your versioning strategy &lt;strong&gt;once&lt;/strong&gt; in a central configuration class. The controllers themselves stay clean — they only use a simple &lt;code&gt;version&lt;/code&gt; attribute on their mapping annotations.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The core idea:&lt;/strong&gt; your controllers declare &lt;em&gt;which version&lt;/em&gt; they serve. A separate configuration class declares &lt;em&gt;how&lt;/em&gt; versioning works (URI, header, query param, media type). This separation of concerns is what makes it powerful.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  1. URI path versioning (Spring 7+)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Controller — declares versions, knows nothing about URL structure&lt;/span&gt;
&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/versions/{v}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VersionController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;defaultVersion&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Response from API 1.0"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2.0+"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// "+" means 2.0 and above&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;v2Version&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Response from API 2.0"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Central config — the only place versioning strategy lives&lt;/span&gt;
&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebConfig&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;WebMvcConfigurer&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;configureApiVersioning&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiVersionConfigurer&lt;/span&gt; &lt;span class="n"&gt;configurer&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;configurer&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;usePathSegment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// pick segment 2 of the URL path for version&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addSupportedVersions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2.0"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"3.0"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Request parameter versioning (Spring 7+)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Controller stays identical — same version attributes&lt;/span&gt;
&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/versions"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VersionController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;defaultVersion&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2.0+"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;v2Version&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Only the config changes — the controller doesn't need to know&lt;/span&gt;
&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebConfig&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;WebMvcConfigurer&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;configureApiVersioning&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiVersionConfigurer&lt;/span&gt; &lt;span class="n"&gt;configurer&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;configurer&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;useQueryParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"version"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// ?version=1.0 in the URL&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addSupportedVersions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2.0"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"3.0"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDefaultVersion&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// fallback when no version specified&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Request header versioning (Spring 7+)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Controller — unchanged. Same version attributes, always.&lt;/span&gt;
&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/versions"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VersionController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;defaultVersion&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2.0+"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;v2Version&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Config: clients now pass X-API-VERSION: 1.0 in request headers&lt;/span&gt;
&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebConfig&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;WebMvcConfigurer&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;configureApiVersioning&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiVersionConfigurer&lt;/span&gt; &lt;span class="n"&gt;configurer&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;configurer&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;useRequestHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-API-VERSION"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addSupportedVersions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2.0"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"3.0"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDefaultVersion&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Media type versioning (Spring 7+)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Controller — identical to all the above. This is the beauty of it.&lt;/span&gt;
&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/versions"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VersionController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;defaultVersion&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2.0+"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;v2Version&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Client sends: Accept: application/vnd.eazyapp+json;v=1.0&lt;/span&gt;
&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebConfig&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;WebMvcConfigurer&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;configureApiVersioning&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ApiVersionConfigurer&lt;/span&gt; &lt;span class="n"&gt;configurer&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;configurer&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;useMediaTypeParameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseMediaType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"application/vnd.eazyapp+json"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
                &lt;span class="s"&gt;"v"&lt;/span&gt;
            &lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addSupportedVersions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2.0"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"3.0"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setDefaultVersion&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice something remarkable: the controller code is &lt;strong&gt;identical across all four strategies&lt;/strong&gt;. Only the &lt;code&gt;WebConfig&lt;/code&gt; changes. Want to switch from URI versioning to header versioning across your entire application? Change one method in one file. Done.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;✅ &lt;strong&gt;What Spring 7 gives you&lt;/strong&gt;&lt;br&gt;
One config file = one source of truth for your entire versioning strategy. Your controllers are clean. Switching strategies costs you exactly one edit. Adding a new supported version is a single method call. This is what "separation of concerns" looks like in practice.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Legacy vs Spring 7 — side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Legacy approach&lt;/th&gt;
&lt;th&gt;Spring 7+&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Version declaration&lt;/td&gt;
&lt;td&gt;Inside every &lt;code&gt;@GetMapping&lt;/code&gt; annotation&lt;/td&gt;
&lt;td&gt;Single &lt;code&gt;version&lt;/code&gt; attribute per method&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strategy config&lt;/td&gt;
&lt;td&gt;Scattered&lt;/td&gt;
&lt;td&gt;Centralised&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Switch strategy&lt;/td&gt;
&lt;td&gt;Rewrite every controller annotation&lt;/td&gt;
&lt;td&gt;Change one line in &lt;code&gt;WebConfig&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add new version&lt;/td&gt;
&lt;td&gt;Manually add method per endpoint&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.addSupportedVersions("3.0")&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default version&lt;/td&gt;
&lt;td&gt;Manual fallback logic&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.setDefaultVersion()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Version range (2.0+)&lt;/td&gt;
&lt;td&gt;Not possible&lt;/td&gt;
&lt;td&gt;Supported natively&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supported in&lt;/td&gt;
&lt;td&gt;All Spring Boot versions&lt;/td&gt;
&lt;td&gt;Spring 7+ / Spring Boot 4+&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Which strategy should you choose?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;Watch out for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;URI Path &lt;code&gt;/v1/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Public APIs, easy testing in browser, most widely understood&lt;/td&gt;
&lt;td&gt;URL proliferation as versions grow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query Param &lt;code&gt;?version=1&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Internal APIs, quick prototyping&lt;/td&gt;
&lt;td&gt;Cache complications — same URL, different responses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Header &lt;code&gt;X-API-VERSION&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Clean URLs, microservice-to-microservice calls&lt;/td&gt;
&lt;td&gt;Harder to test without proper tools like Postman&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Media Type&lt;/td&gt;
&lt;td&gt;Strict REST compliance, enterprise APIs&lt;/td&gt;
&lt;td&gt;Steeper learning curve, verbose Accept headers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For most projects — especially public-facing APIs and portfolio projects — &lt;strong&gt;URI path versioning is the pragmatic choice&lt;/strong&gt;. It's explicit, easy to test, simple to document, and universally understood by developers of all experience levels. Move to header or media type versioning only when clean URLs and strict REST purity become actual requirements.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Interview insight&lt;/strong&gt;&lt;br&gt;
In interviews, if asked about API versioning, most candidates mention URI versioning and stop there. Stand out by explaining all four strategies, their trade-offs, and then discussing the Spring 7 centralised configuration model. It shows you understand not just &lt;em&gt;what&lt;/em&gt; to do but &lt;em&gt;why&lt;/em&gt; the new approach is a better design.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;API versioning is one of those things that feels optional until the day it isn't. The FoodRush disaster was fictional, but the scenario plays out in real companies every year. A well-intentioned improvement, deployed without versioning, breaks clients silently and erodes the trust of everyone building on top of your API.&lt;/p&gt;

&lt;p&gt;The legacy Spring approach gave developers the tools to version APIs but left the housekeeping entirely up to them — scattered across annotations, mixed into business logic, painful to change. Spring 7's &lt;code&gt;configureApiVersioning&lt;/code&gt; method finally gives versioning a proper home: one place, one strategy, one source of truth.&lt;/p&gt;

&lt;p&gt;Whatever strategy you pick — URI, header, query param, or media type — the discipline of versioning your API from day one is the real lesson. Change is inevitable. How gracefully your clients survive that change is entirely in your hands.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Written as part of a Spring Boot deep dive. Code examples use Spring 7+ / Spring Boot 4.x with the new &lt;code&gt;ApiVersionConfigurer&lt;/code&gt; API. Legacy examples are compatible with Spring Boot 2.x and 3.x.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>spring</category>
      <category>java</category>
      <category>api</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
