top of page

API Versioning

  • Writer: Anand Nerurkar
    Anand Nerurkar
  • May 13
  • 4 min read

how to handle api versioning


Handling API versioning is critical for SaaS platforms — especially multi-tenant architectures — to support backward compatibility, safe upgrades, and differentiated feature sets across tenants.

API Versioning: Key Goals

  • Backward compatibility for existing clients

  • Safe evolution of APIs with new features

  • Support multiple versions in parallel for gradual rollout

  • Enable per-tenant feature control (optional)

🔧 Common API Versioning Strategies

1. URI Path Versioning (Most Common & RESTful)

bash

GET /api/v1/customers

GET /api/v2/customers


✅ Pros:

  • Simple and explicit

  • Easy to manage in routing/gateways

  • Developers see version clearly

❌ Cons:

  • Requires duplicating routes/controllers per version

2. Header-Based Versioning

http

GET /api/customers

Header: X-API-Version: v2

✅ Pros:

  • Cleaner URIs

  • Allows more dynamic routing/version control

❌ Cons:

  • Less visible to end users

  • Some tools (e.g., Swagger) need extra config

3. Query Parameter Versioning

bash

GET /api/customers?api-version=2

✅ Pros:

  • Easy to implement

  • Works well with API Gateway or custom logic

❌ Cons:

  • Feels less RESTful

  • Can cause caching issues

4. Media Type Versioning (Content Negotiation)

http

Accept: application/vnd.myapi.v1+json


✅ Pros:

  • Full control via headers

  • Aligns with hypermedia practices

❌ Cons:

  • Complex to implement and test

  • Hard to discover

🧱 Recommended Strategy for SaaS on Azure

Use URI versioning for major versionsCombine with feature flags or headers for minor feature control

🔁 Versioning Strategy in Microservices or Spring Boot + Azure

🔹 Codebase Structure

  • Use packages like com.insurance.api.v1.controller

  • Use Spring Profiles or API version annotations

  • Keep shared business logic in common libraries

🔹 API Gateway (Azure API Management)

  • Define multiple versions for the same API

  • Route /v1, /v2 endpoints to respective backend services

  • Apply version headers to control access if needed

🛠️ Azure Services to Support API Versioning

Component

Azure Service

Usage

API Gateway

Azure API Management

Route versioned APIs, policies

Feature Control

Azure App Configuration

Per-tenant feature toggles

Authentication

Azure AD B2C

Token claims to carry version or tenant info

Deployment Mgmt

Azure DevOps

Canary deploys for new versions

Documentation

Swagger / OpenAPI

Version-specific docs (/v1/api-docs)

📋 Best Practices

  1. Never break existing versions → Keep old versions until clients migrate.

  2. Deprecate with notice → Mark versions as deprecated, notify clients.

  3. Use semver: Major (breaking), minor (new), patch (bugfix)

  4. Monitor usage → Track which versions tenants are using.

  5. Automate with CI/CD → Build, test, and deploy each version.

📦 Sample URI Strategy

API Call

Description

GET /api/v1/claims

Old claims format

GET /api/v2/claims

New model, more fields

POST /api/v1/claims/submit

Legacy submission flow

POST /api/v2/claims/submit

New flow + fraud check


Spring Boot implementation guide


Here’s a Spring Boot implementation guide for API versioning, designed specifically for a multi-tenant SaaS architecture with major version control and optional per-tenant feature flags.

Goals of This Guide

  • Implement clean API versioning using Spring Boot

  • Support multiple API versions (v1, v2, etc.)

  • Allow per-tenant control if needed

  • Ensure maintainable structure for long-term evolution

🏗️ 1. Folder Structure (Per Version)

css

src/main/java

│ ├── controller

│ │ ├── v1

│ │ │ └── ClaimControllerV1.java

│ │ └── v2

│ │ └── ClaimControllerV2.java

│ ├── service

│ │ ├── ClaimService.java (shared)

│ └── model

│ ├── v1

│ │ └── ClaimRequestV1.java

│ └── v2

│ └── ClaimRequestV2.java

📘 2. Controller Implementation

java

@RestController

@RequestMapping("/api/v1/claims")

public class ClaimControllerV1 {


@Autowired

private ClaimService claimService;


@GetMapping("/{id}")

public ResponseEntity<ClaimResponseV1> getClaim(@PathVariable Long id) {

return ResponseEntity.ok(claimService.getClaimV1(id));

}

}

java

@RestController

@RequestMapping("/api/v2/claims")

public class ClaimControllerV2 {


@Autowired

private ClaimService claimService;


@GetMapping("/{id}")

public ResponseEntity<ClaimResponseV2> getClaim(@PathVariable Long id) {

return ResponseEntity.ok(claimService.getClaimV2(id));

}

}

🧠 3. Shared Service Layer

java

@Service

public class ClaimService {


public ClaimResponseV1 getClaimV1(Long id) {

// Map internal model to V1 format

}


public ClaimResponseV2 getClaimV2(Long id) {

// Map internal model to V2 format (with more fields, logic)

}

}

🛡️ 4. Optional: Header-Based Versioning (Advanced)

Instead of URL versioning, use X-API-Version header:

🔸 Custom @ApiVersion Annotation

java

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

public @interface ApiVersion {

String value();

}

🔸 Version-Aware Request Mapping Handler

Use Spring’s RequestMappingHandlerMapping to route based on custom header:

java

@Configuration

public class ApiVersioningConfig extends WebMvcConfigurationSupport {


@Override

protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {

return new ApiVersionRequestMappingHandlerMapping();

}

}

💡 For simplicity, URL versioning is preferred unless you require dynamic versioning via headers.

🧪 5. Swagger Integration for Multiple Versions

Add Swagger Config per Version:

java
  • @Bean

    public Docket apiV1() {

    return new Docket(DocumentationType.OAS_30)

    .groupName("v1")

    .select()

    .apis(RequestHandlerSelectors.basePackage("com.example.controller.v1"))

    .paths(PathSelectors.any())

    .build()

    .apiInfo(apiInfo("1.0"));

    }


    @Bean

    public Docket apiV2() {

    return new Docket(DocumentationType.OAS_30)

    .groupName("v2")

    .select()

    .apis(RequestHandlerSelectors.basePackage("com.example.controller.v2"))

    .paths(PathSelectors.any())

    .build()

    .apiInfo(apiInfo("2.0"));

    }

🧩 6. Azure API Management Mapping (Optional)

In Azure APIM:

  • Create multiple API versions: /api/v1/*, /api/v2/*

  • Route them to Spring Boot backend via appropriate path mapping or header

  • You can also restrict older versions per tenant using policies

📊 7. Per-Tenant Version Control (Optional)

Use a Feature Management System (e.g., Azure App Configuration, LaunchDarkly):

java

if (tenantConfig.get("api-version").equals("v2")) {

return claimService.getClaimV2(id);

} else {

return claimService.getClaimV1(id);

}

You can also use:

  • TenantContext extracted from JWT token or header

  • Spring AOP for conditional routing based on tenant features

🧼 Best Practices

Practice

Why it Matters

Use clear versioned URLs

Easier documentation and testing

Keep backward compatibility

Avoid breaking existing tenants

Document deprecated versions

Help tenants plan migration

Automate tests for each API version

Ensure consistent behavior

Apply tenant-specific feature toggles

Gradual rollouts, A/B testing support


 
 
 

Recent Posts

See All
Ops Efficiency 30 % improvement

how did you achieve 30 % operational efficiency Achieving 30% operational efficiency in a BFSI-grade, microservices-based personal...

 
 
 

Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
  • Facebook
  • Twitter
  • LinkedIn

©2024 by AeeroTech. Proudly created with Wix.com

bottom of page