Maintaining backward compatibility is critical for API evolution. Poor versioning strategies can break client applications, increase support costs, and damage developer trust.
Backward compatibility means existing clients continue functioning when API changes occur. There are three levels:
GET /v1/products
GET /v2/products
Pros:
Cons:
GET /products
Accept: application/vnd.company.api+json;version=2
Pros:
Cons:
GET /products
Accept: application/vnd.company.api.v2+json
Recommended by REST standards (RFC 6838). Provides the most flexibility.
components:
schemas:
Product:
type: object
properties:
id:
type: string
name:
type: string
# New optional field
description:
type: string
nullable: true
Rules for backward-compatible schema changes:
Example deprecation headers:
GET /v1/products
Deprecation: true
Sunset: Wed, 31 Dec 2025 23:59:59 GMT
Link: </v2/products>; rel="successor-version"
Recommended timeline:
class ProductAdapter:
def to_v1(self, product):
return {
'id': product.id,
'name': product.name
# v2 fields intentionally omitted
}
def to_v2(self, product):
return {
'id': product.id,
'name': product.name,
'description': product.description or None
}
// Config service
{
"api": {
"enable_v2_products": false
}
}
// Route handler
app.get('/products', (req, res) => {
if (config.api.enable_v2_products) {
return new ProductControllerV2().handle(req, res)
}
return new ProductControllerV1().handle(req, res)
})
@RestController
@RequestMapping("/products")
public class ProductController {
@GetMapping(produces = "application/vnd.company.api.v1+json")
public ResponseEntity<ProductV1> getV1() {
// ...
}
@GetMapping(produces = "application/vnd.company.api.v2+json")
public ResponseEntity<ProductV2> getV2() {
// ...
}
}
Stripe maintains exceptional backward compatibility through:
GET /v1/charges/ch_123?expand[]=customer
Feature: Backward Compatibility
Scenario: Existing clients receive expected responses
Given API version v1 exists
When I request /v1/products
Then response should contain:
"""
{
"id": "string",
"name": "string"
}
"""
# .github/workflows/api-check.yml
name: API Version Check
on: [pull_request]
jobs:
check-breaking-changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: stoplightio/spectral-action@v1
with:
file: openapi.yaml
ruleset: stoplightio/api-standards
Backward compatibility impacts:
Mitigation strategies:
GET /products?fields=id,name
For large-scale systems, consider API gateways with built-in version transformation capabilities to minimize code changes. Always measure the performance impact of compatibility layers and optimize based on usage patterns.