API Versioning

API versioning manages incompatible contract changes while allowing existing clients to continue operating. It provides a controlled compatibility boundary so producers can evolve an API without unexpectedly breaking consumers.

An API version identifies a particular contract that clients can depend on. When a change is backward-compatible, such as adding an optional response field, a new version may not be necessary; breaking changes, such as removing a field or changing its meaning, generally require a compatibility strategy such as a new version.

Common approaches include URI versioning such as `/api/v2/orders`, header-based versioning, query-parameter versioning, and media-type versioning. URI versioning is highly visible and easy to use, while header or media-type approaches keep the resource URI independent of the representation version.

For a Java backend, versioning should be considered alongside DTOs, validation, serialization, documentation, and persistence models. A useful boundary is to keep version-specific request and response models separate from internal domain models so that an external contract change does not unnecessarily propagate through the application.

Versioning is also a lifecycle concern. A team should define how long old versions remain supported, how clients are notified, how deprecated versions are measured, and how migration is handled. Supporting multiple versions increases testing and operational complexity, so versions should not be created merely for every small additive change.

Key points

Interview questions

How do you deprecate an old API version safely?

First, publish a migration and support policy with clear dates and replacement documentation. Instrument the old version so the team can identify active clients and usage patterns rather than removing it blindly. Communicate the replacement contract and provide clients with enough transition time. After usage reaches an acceptable state, the old version can be retired according to the documented policy.

When should an API get a new version?

A new version is generally appropriate when a change can break existing clients. Examples include removing a field, changing a field's type or semantics, or changing required request behavior. Purely additive changes can often be introduced without a new version if existing clients continue to work. The decision should be based on the compatibility contract rather than the size of the code change.

What are common API versioning strategies?

Common strategies include putting the version in the URI, using a request header, using a query parameter, or using media-type negotiation through the `Accept` header. URI versioning is explicit and straightforward for clients and infrastructure. Header and media-type approaches keep the resource URI stable but require clients and tooling to handle version metadata correctly. The choice should consider discoverability, caching, tooling, and the organization's existing API conventions.

How would you structure a Java service that supports V1 and V2 simultaneously?

I would expose separate version-specific controllers or endpoint mappings and use separate request and response DTOs for each public contract. Shared business logic can usually remain in services that operate on internal domain objects. Mapping code translates between each API DTO and the internal model. This prevents version-specific JSON concerns from spreading throughout the business layer and makes each contract easier to test independently.

Should API versions map directly to database versions?

No. They solve different compatibility problems and should generally evolve independently. An API version represents an external contract, while a database migration represents a change to persistence structure. A stable API can often survive several database migrations as long as the application maintains the required external behavior. Keeping the layers decoupled also makes rolling deployments and internal refactoring easier.