# Versioning Strategies

These are the following ways in which we can do the API versioning -

* URI versioning
* Header versioning
* Content negotiation
* Version migration strategies

### 1. URI Path Versioning

This is the most straightforward approach, where the URL path includes the version details.&#x20;

**Example**

```plaintext
GET /v1/users
GET /v2/users
```

**Pros**

* **Clear and Explicit:** The version is immediately visible to developers in the URI.
* **Cache-Friendly:** Works well with caching systems since the version is part of the URL.
* **Backward Compatibility:** Allows clients to continue using older versions while new versions are introduced.

**Cons**

* **Clutters URLs:** Adds redundancy to the URI.
* **Limited Flexibility:** Changing versions requires updating the client’s integration.

```java
@RestController
@RequestMapping("/api/v1")
public class OrderControllerV1 {
    
    @GetMapping("/orders/{orderId}")
    public ResponseEntity<OrderResponseV1> getOrder(@PathVariable String orderId) {
        // V1 implementation
        return ResponseEntity.ok(orderService.getOrderV1(orderId)); // v1 service
    }
}

@RestController
@RequestMapping("/api/v2")
public class OrderControllerV2 {
    
    @GetMapping("/orders/{orderId}")
    public ResponseEntity<OrderResponseV2> getOrder(@PathVariable String orderId) {
        // V2 implementation with enhanced response
        return ResponseEntity.ok(orderService.getOrderV2(orderId)); // v2 service 
    }
}
```

### 2. Header Versioning

This approach uses custom headers to specify the API version.

**Pros**

* **Clean URIs:** Keeps the URL clean and focused on resource identification.
* **Flexible:** Allows finer-grained control, such as specifying minor versions (`1.1`, `1.2`).

**Cons**

* **Less Discoverable:** The version information is not visible in the URL.
* **Cache Complexity:** Requires more sophisticated caching mechanisms since caching is based on headers.

**Best Practices**

* Use the `Accept` header with media type versioning.\
  **Example:** `Accept: application/vnd.api+json; version=2.0`
* Alternatively, use a custom header.\
  **Example:** `X-API-Version: 2`

```java
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    
    @GetMapping("/{orderId}")
    public ResponseEntity<?> getOrder(
            @PathVariable String orderId,
            @RequestHeader(value = "X-API-Version", defaultValue = "1") int version) {
        
        switch (version) {
            case 1:
                OrderResponseV1 responseV1 = orderService.getOrderV1(orderId);
                return ResponseEntity.ok(responseV1);
                
            case 2:
                OrderResponseV2 responseV2 = orderService.getOrderV2(orderId);
                return ResponseEntity.ok(responseV2);
                
            default:
                throw new UnsupportedVersionException("API version " + version + " is not supported");
        }
    }
}

// Version handler configuration
@Configuration
public class VersioningConfig {
    
    @Bean
    public WebMvcConfigurer versioningConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new ApiVersionInterceptor());
            }
        };
    }
}
```

Api Version Interceptor

```java
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;

public class ApiVersionInterceptor implements HandlerInterceptor {
    
    private static final Logger logger = LoggerFactory.getLogger(ApiVersionInterceptor.class);
    private static final String VERSION_HEADER = "X-API-Version";
    private static final int MIN_VERSION = 1;
    private static final int MAX_VERSION = 2;

    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
        
        String versionHeader = request.getHeader(VERSION_HEADER);
        
        // If no version header is provided, default to version 1
        if (versionHeader == null || versionHeader.isEmpty()) {
            request.setAttribute("api-version", 1);
            logger.debug("No API version specified. Defaulting to version 1");
            return true;
        }

        try {
            int version = Integer.parseInt(versionHeader);
            
            // Validate version range
            if (version < MIN_VERSION || version > MAX_VERSION) {
                logger.warn("Unsupported API version requested: {}", version);
                response.setStatus(HttpStatus.BAD_REQUEST.value());
                response.getWriter().write(
                    String.format("API version %d is not supported. Supported versions are %d through %d",
                                version, MIN_VERSION, MAX_VERSION)
                );
                return false;
            }

            // Store the version in request attributes for later use
            request.setAttribute("api-version", version);
            logger.debug("API Version {} validated successfully", version);
            return true;

        } catch (NumberFormatException e) {
            logger.error("Invalid API version format: {}", versionHeader);
            response.setStatus(HttpStatus.BAD_REQUEST.value());
            response.getWriter().write("Invalid API version format. Version must be a number");
            return false;
        }
    }

    @Override
    public void postHandle(HttpServletRequest request, 
                         HttpServletResponse response, 
                         Object handler, 
                         ModelAndView modelAndView) throws Exception {
        // Add version header to response for clarity
        int version = (int) request.getAttribute("api-version");
        response.setHeader(VERSION_HEADER, String.valueOf(version));
    }

    @Override
    public void afterCompletion(HttpServletRequest request, 
                              HttpServletResponse response, 
                              Object handler, 
                              Exception ex) throws Exception {
        // Cleanup if needed
        request.removeAttribute("api-version");
    }
}
```

### 3. Accept Header Versioning (Content Negotiation)

This strategy uses content negotiation through the Accept header. It determines the API version based on the `Content-Type` or `Accept` headers.

**Example**

```http
GET /users
Accept: application/vnd.company.users+json; version=1
```

**Pros**

* **Advanced Flexibility:** Supports versioning, content formats, and feature negotiation simultaneously.
* **Clean URIs:** Does not add version information to the URL.

**Cons**

* **Complex Implementation:** More challenging to implement and configure on the server side.
* **Discoverability:** Version information is less obvious compared to URI versioning.

**Best Practices**

* Use MIME types for content negotiation.\
  **Example:** `application/vnd.company.resource-v1+json`
* Clearly document available content types and their associated versions.

```java
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    
    @GetMapping(
        value = "/{orderId}",
        produces = "application/vnd.company.app-v1+json"
    )
    public ResponseEntity<OrderResponseV1> getOrderV1(@PathVariable String orderId) {
        return ResponseEntity.ok(orderService.getOrderV1(orderId));
    }
    
    @GetMapping(
        value = "/{orderId}",
        produces = "application/vnd.company.app-v2+json"
    )
    public ResponseEntity<OrderResponseV2> getOrderV2(@PathVariable String orderId) {
        return ResponseEntity.ok(orderService.getOrderV2(orderId));
    }
}
```

### 4. Query Parameter Versioning

While not as common, some APIs use query parameters for versioning.

```java
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    
    @GetMapping("/{orderId}")
    public ResponseEntity<?> getOrder(
            @PathVariable String orderId,
            @RequestParam(defaultValue = "1") int version) {
        
        return switch (version) {
            case 1 -> ResponseEntity.ok(orderService.getOrderV1(orderId));
            case 2 -> ResponseEntity.ok(orderService.getOrderV2(orderId));
            default -> throw new UnsupportedVersionException("Version not supported");
        };
    }
}
```

### Advanced Versioning Strategies

#### Feature Toggles with Versioning

```java
@Service
public class OrderService {
    
    private final FeatureToggleService featureToggleService;
    
    public OrderResponse getOrder(String orderId, int version) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new ResourceNotFoundException("Order not found"));
            
        OrderResponse.OrderResponseBuilder builder = OrderResponse.builder()
            .orderId(order.getId())
            .status(order.getStatus());
            
        // Add features based on version and toggles
        if (version >= 2 && featureToggleService.isEnabled("ENHANCED_ORDER_DETAILS")) {
            builder.enhancedDetails(getEnhancedDetails(order));
        }
        
        if (version >= 2 && featureToggleService.isEnabled("ORDER_ANALYTICS")) {
            builder.analytics(getOrderAnalytics(order));
        }
        
        return builder.build();
    }
}
```

#### Graceful Deprecation

```java
@RestController
@RequestMapping("/api/v1")
public class OrderControllerV1 {
    
    @GetMapping("/orders/{orderId}")
    @Deprecated
    @Header("Deprecation: date=2024-12-31")
    @Header("Link: </api/v2/orders/{orderId}>; rel=\"successor-version\"")
    public ResponseEntity<OrderResponseV1> getOrder(@PathVariable String orderId) {
        // Log deprecation metrics
        deprecationMetricsService.recordDeprecatedEndpointUsage(
            "GET /api/v1/orders/{orderId}");
            
        return ResponseEntity.ok(orderService.getOrderV1(orderId));
    }
}
```

### Best Practices for API Versioning

1. **Version Strategy Selection** Consider these factors when choosing a strategy:

   * Client requirements
   * Infrastructure constraints
   * Developer experience
   * Maintenance overhead

2. **Backwards Compatibility**

   ```java
   public class OrderResponseV2 implements OrderResponse {
       // New fields
       private List<OrderItemV2> items;
       private EnhancedShippingDetails shipping;
       
       // Backward compatibility method
       public OrderResponseV1 toV1Format() {
           return OrderResponseV1.builder()
               .orderId(this.orderId)
               .status(this.status)
               .items(this.items.stream()
                   .map(OrderItemV2::toV1Format)
                   .collect(Collectors.toList()))
               .build();
       }
   }
   ```

3. **Documentation**

   ```java
   @OpenAPIDefinition(
       info = @Info(
           title = "E-commerce API",
           version = "2.0",
           description = "API documentation with version support"
       )
   )
   @RestController
   public class OrderController {
       
       @Operation(summary = "Get order details", 
                 description = "Version 2 includes enhanced order details")
       @ApiResponse(
           responseCode = "200",
           description = "Order retrieved successfully",
           content = @Content(schema = @Schema(implementation = OrderResponseV2.class))
       )
       @GetMapping(
           value = "/api/v2/orders/{orderId}",
           produces = "application/json"
       )
       public ResponseEntity<OrderResponseV2> getOrderV2(...) {
           // Implementation
       }
   }
   ```

#### **Comparison of Strategies**

<table data-header-hidden data-full-width="true"><thead><tr><th></th><th></th><th></th><th></th><th></th><th></th></tr></thead><tbody><tr><td><strong>Strategy</strong></td><td><strong>Visibility</strong></td><td><strong>Ease of Use</strong></td><td><strong>Flexibility</strong></td><td><strong>Cache Support</strong></td><td><strong>Common Use Case</strong></td></tr><tr><td>URI Versioning</td><td>High (in URL)</td><td>Easy</td><td>Low</td><td>Excellent</td><td>Public APIs with simple versioning needs.</td></tr><tr><td>Header Versioning</td><td>Low (in headers)</td><td>Moderate</td><td>High</td><td>Complex</td><td>APIs requiring clean URLs and flexibility.</td></tr><tr><td>Content Negotiation</td><td>Low (in headers)</td><td>Complex</td><td>Very High</td><td>Moderate</td><td>Advanced APIs with multiple content types.</td></tr></tbody></table>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wisdom.gitbook.io/gyan/core/rest-apis-design/versioning-strategies.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
