APIs evolve over time. New business requirements, improved data models, enhanced security, and additional features often require changes to existing endpoints. While these changes improve the API, they can also break applications that depend on older versions.

A production API should support evolution without forcing every client to upgrade immediately. This is where API versioning becomes essential. By introducing versioning, developers can add new functionality, deprecate old behavior, and migrate clients gradually without disrupting existing integrations.

In this article, you'll learn how to implement API versioning in ASP.NET Core 10, compare different versioning strategies, and design APIs that remain backward compatible as they evolve.

Why API Versioning Matters

The Problem with Breaking Changes

Imagine an e-commerce API with the following response:

{
    "id": 101,
    "name": "Laptop",
    "price": 1200
}

Months later, the development team changes the response to:

{
    "productId": 101,
    "productName": "Laptop",
    "price": 1200,
    "currency": "USD"
}

Although the new response is more descriptive, existing mobile apps, web applications, and third-party integrations expecting id and name will fail.

API versioning allows both versions to coexist while consumers migrate at their own pace.

Common API Versioning Strategies

URL Versioning

The version is included in the URL.

/api/v1/products
/api/v2/products

Advantages

  • Easy to understand

  • Visible in logs

  • Simple routing

  • Widely adopted

Considerations

Every new version introduces additional endpoints that must be maintained until older versions are retired.

Query String Versioning

The version is passed as a query parameter.

/api/products?api-version=1.0

Advantages

  • Existing URLs remain unchanged

  • Easy for testing

Considerations

Version information is less visible, making documentation and debugging slightly more difficult.

Header Versioning

Clients specify the version using an HTTP header.

api-version: 2.0

Advantages

  • Clean URLs

  • Flexible client implementations

Considerations

Headers are less discoverable and require additional configuration in API clients and testing tools.

Configuring API Versioning

Register API versioning during application startup.

builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);

    options.AssumeDefaultVersionWhenUnspecified = true;

    options.ReportApiVersions = true;
});

Why This Configuration?

This configuration establishes version 1.0 as the default version while allowing existing clients to continue working even if they don't explicitly specify a version.

Enabling ReportApiVersions adds supported API versions to response headers, helping consumers discover available versions.

Versioning Controllers

A controller can explicitly declare the API version it supports.

[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/products")]
[ApiController]
public class ProductsController : ControllerBase
{
}

Why Version Controllers?

Explicit version attributes make supported versions clear while allowing multiple controller implementations to coexist.

Each controller focuses on one API contract, reducing complexity and making future maintenance easier.

Supporting Multiple Versions

An API can expose different implementations simultaneously.

Version 1:

GET /api/v1/products

Version 2:

GET /api/v2/products

Both versions can operate independently, allowing organizations to migrate consumers gradually instead of forcing immediate upgrades.

End-to-End Implementation

Consider an online retail platform.

Architecture:

Web App
     │
Mobile App
     │
Partner API
     │
     ▼
API Gateway
     │
 ┌───┴──────────────┐
 ▼                  ▼
Version 1       Version 2
     │                  │
Business Services
     │
SQL Database

Migration workflow:

  1. Version 1 remains available for existing clients.

  2. Version 2 introduces additional product information.

  3. New applications adopt Version 2.

  4. Existing consumers continue using Version 1 without interruption.

  5. Usage metrics identify when Version 1 can be safely retired.

  6. After communicating the deprecation timeline, Version 1 is removed.

This approach enables continuous API evolution while minimizing disruption for consumers.

Deprecating Older Versions

Eventually, outdated API versions should be retired.

A recommended migration process includes:

  1. Announce deprecation early.

  2. Publish migration documentation.

  3. Encourage adoption of the newer version.

  4. Monitor client usage.

  5. Remove deprecated versions after the announced support period.

Deprecation should be planned carefully to avoid unexpected outages for downstream consumers.

Versioning Strategy Comparison

StrategyAdvantagesConsiderations
URL VersioningSimple, visible, widely adoptedAdditional routes
Query StringEasy testingLess discoverable
Header VersioningClean URLsMore client configuration
Media Type VersioningFlexibleHigher implementation complexity

URL versioning remains the most common choice because it is easy to understand, document, and troubleshoot.

Best Practices

  • Introduce versioning before the first breaking change.

  • Keep existing versions stable.

  • Avoid unnecessary version increments.

  • Document version differences clearly.

  • Support multiple versions during migration.

  • Monitor usage of older versions.

  • Establish a published deprecation policy.

  • Use semantic versioning where appropriate.

  • Test every supported version independently.

Common Mistakes

One common mistake is modifying an existing API contract instead of introducing a new version. Even small property name changes can break client applications.

Another issue is maintaining too many API versions indefinitely. Supporting outdated versions increases maintenance effort and testing complexity.

Developers also sometimes duplicate business logic across versions. Whenever possible, keep shared logic in application services while exposing different API contracts through version-specific controllers.

Testing and Validation

Each API version should be validated independently.

Recommended testing includes:

  • Endpoint compatibility

  • Response schema validation

  • Authentication and authorization

  • Integration testing

  • Contract testing

  • Performance testing

  • Regression testing

  • Backward compatibility verification

Automated tests help ensure that introducing a new version does not unintentionally affect existing clients.

Performance Considerations

API versioning itself introduces minimal overhead, but supporting multiple versions can increase application complexity.

Consider these recommendations:

  • Share business logic between versions.

  • Cache frequently requested responses.

  • Optimize database queries.

  • Remove deprecated versions when appropriate.

  • Monitor version-specific traffic.

  • Keep routing configurations straightforward.

Well-designed versioning strategies allow applications to evolve without significant performance impact.

Security Considerations

Security requirements apply consistently across all API versions.

Follow these practices:

  • Enforce authentication and authorization for every version.

  • Apply security updates to supported versions.

  • Validate all inputs.

  • Use HTTPS for every endpoint.

  • Audit API access.

  • Monitor deprecated versions for unusual activity.

  • Avoid exposing sensitive implementation details through version-specific responses.

Older versions should continue receiving critical security updates until they are officially retired.

Troubleshooting

Clients Receive 404 Errors

Verify that the requested API version matches a registered route and that routing is configured correctly.

Incorrect Version Is Selected

Review the versioning configuration and confirm whether the application expects the version through the URL, query string, or HTTP headers.

Duplicate Controllers Cause Routing Conflicts

Ensure each controller explicitly declares the API version it supports and uses the correct versioned route template.

Clients Continue Using Deprecated Versions

Monitor API usage analytics, communicate deprecation timelines clearly, and provide migration guidance before removing support.

Conclusion

API versioning is essential for building long-lived ASP.NET Core applications that can evolve without breaking existing consumers. By choosing an appropriate versioning strategy, maintaining backward compatibility, and planning migrations carefully, development teams can introduce new capabilities while preserving a stable experience for existing clients. Whether your APIs serve web applications, mobile apps, or third-party integrations, a well-designed versioning strategy ensures continuous evolution with minimal disruption.