2026-08-06 17:06:04.850824+08 / restful-api
Compatibility
Guidelines for backward compatibility, breaking changes, versioning, and consumer robustness in RESTful APIs.
Compatibility
1. Overview
Backward compatibility is one of the most important principles in API evolution.
An API is a contract between service providers and consumers. API providers should avoid breaking existing clients through unilateral changes because consumers usually have independent release cycles and require system stability.
When evolving an API, prefer compatible extensions instead of introducing incompatible changes.
If an incompatible change is unavoidable, a new major API version should be introduced, allowing existing consumers to continue using the previous version during an agreed migration period.
2. Backward Compatibility Within Major Versions
APIs MUST NOT introduce breaking changes within the same major version.
Compatible API evolution should follow these principles:
- Existing clients should continue working without modification.
- New functionality should be introduced through additive changes.
- API consumers should be able to ignore unknown fields or values introduced by future compatible versions.
Breaking changes should only be introduced by increasing the major version.
Example:
/api/orders/v1/orders
A breaking change requires a new major version:
/api/orders/v2/orders
3. Prefer Compatible Extensions
API providers SHOULD prefer backward-compatible extensions.
The following practices help maintain compatibility.
3.1 Add Optional Fields Only
New fields added to request or response objects should always be optional.
Good:
{
"id": "123",
"name": "example",
"description": "optional field"
}
Adding description does not break existing clients.
Avoid:
{
"id": "123",
"name": "example",
"mandatoryField": "new-value"
}
Adding required fields forces clients to update.
3.2 Do Not Change Field Meaning
The semantic meaning of existing fields MUST NOT change.
Example:
Before:
{
"customerNumber": "10001"
}
Changing the meaning of customerNumber from one identifier type to another breaks consumers.
If a new concept is required, introduce a new field instead.
3.3 Keep Existing Validation Rules
Input validation rules should not become more restrictive in compatible versions.
Example:
Existing API:
username length: 1-100 characters
Changing it to:
username length: 5-20 characters
may break existing clients.
Validation constraints should be clearly documented.
4. Enum Compatibility
Enums require special attention during API evolution.
Response Enum
Response values should not be extended unexpectedly.
Example:
Existing response:
{
"status": "ACTIVE"
}
Allowed values:
ACTIVE
INACTIVE
Adding:
PENDING
may break clients that assume only existing values.
Request Enum
Request enums can be extended.
Example:
Client sends:
{
"type": "STANDARD"
}
A future API version may allow:
STANDARD
PREMIUM
ENTERPRISE
Existing clients remain compatible.
5. Response Structure Compatibility
API responses should be designed for future extensions.
Always Return JSON Objects
The top-level response structure SHOULD always be a JSON object.
Recommended:
{
"items": [
{
"id": "1",
"name": "example"
}
]
}
Avoid:
[
{
"id": "1",
"name": "example"
}
]
Because arrays cannot easily be extended with additional metadata.
For example, pagination requires additional fields:
{
"items": [
{
"id": "1"
}
],
"nextPage": "/items?page=2"
}
6. OpenAPI Extension Rules
OpenAPI schemas should be considered extensible by default.
Clients consuming API responses:
- MUST NOT assume objects are closed.
- MUST ignore unknown fields.
- SHOULD tolerate additional properties.
Example:
Current response:
{
"id": "123",
"name": "test"
}
Future response:
{
"id": "123",
"name": "test",
"createdAt": "2026-08-06"
}
Old clients should continue working.
7. Consumer Robustness Principle
API consumers SHOULD follow the robustness principle:
Be conservative when sending requests, but tolerant when processing responses.
Consumers should:
Ignore Unknown Fields
Example:
{
"id": "123",
"name": "test",
"newField": "future extension"
}
Clients should process known fields and ignore unknown ones.
Handle New Enum Values
Clients should not fail when receiving new optional enum values.
Example:
Existing:
ACTIVE
DISABLED
Future:
ACTIVE
DISABLED
SUSPENDED
Unknown values should be handled gracefully.
Handle Unknown HTTP Status Codes
HTTP status codes are extensible.
Clients should provide fallback handling.
Example:
Known:
200 OK
400 Bad Request
404 Not Found
Future:
422 Unprocessable Entity
Clients should handle unexpected codes appropriately.
8. Breaking Changes
A breaking change requires clients to modify their implementation.
Typical breaking changes include:
Rename Resource or Method
Before:
GET /users/{id}
After:
GET /customers/{id}
Existing clients fail.
Change URL Structure
Before:
/orders/v1/items
After:
/orders/v1/order-items
The old client path no longer works.
Remove Existing Fields
Before:
{
"id": "1",
"name": "test"
}
After:
{
"id": "1"
}
Removing name breaks consumers.
Rename Existing Fields
Before:
{
"customerId": "100"
}
After:
{
"clientId": "100"
}
Field renaming is a breaking change.
Change Response Codes
Before:
404 Not Found
After:
400 Bad Request
Clients depending on status codes may fail.
Change Error Structure
Error responses are part of the API contract.
Changing:
{
"errorCode": "USER_NOT_FOUND",
"message": "User does not exist"
}
to:
{
"code": "404",
"reason": "missing"
}
may break error handling logic.
9. Versioning Strategy
Use semantic versioning:
MAJOR.MINOR.PATCH
Example:
1.2.3
Meaning:
- MAJOR: incompatible changes
- MINOR: backward-compatible features
- PATCH: backward-compatible fixes
10. API Version in URI
The major version should be included in the URI.
Example:
https://api.example.com/order-service/v1/orders
Breaking change:
https://api.example.com/order-service/v2/orders
Only major version should appear in stable API paths.
11. Content Negotiation
When supporting multiple representations, APIs should support content negotiation.
Example:
Request:
GET /orders/v1/orders/123
Accept: application/json
Response:
Content-Type: application/json
Alternative:
GET /orders/v1/orders/123
Accept: application/yaml
Response:
Content-Type: application/yaml
12. Schema Version Negotiation
When multiple schema versions are supported, clients may request a specific version.
Example:
Request:
GET /orders/v1/orders/123
Accept-Version: 1.2.0
Response:
HTTP/1.1 200 OK
Content-Type: application/json
Schema-Version: 1.2.0
If the requested version is unavailable:
HTTP/1.1 406 Not Acceptable
should be returned.
13. Schema Location
APIs SHOULD provide a link to the schema definition.
Example:
Response:
Schema-Version: 1.2.0
Schema-Location: https://api.example.com/schema/order.json
This allows consumers to discover the exact payload definition.
14. URI Naming Guidelines
Use Lowercase Host Names
Recommended:
https://api.example.com
Avoid:
https://API.Example.COM
Recommended Host Format
api.<namespace>.<domain>
Example:
api.order.example.com
Summary
A well-designed REST API should evolve without breaking existing consumers.
Core principles:
- Prefer additive changes over breaking changes.
- Never remove or rename existing API contracts.
- Add optional fields only.
- Keep response objects extensible.
- Make clients tolerant of future extensions.
- Introduce a new major version only when compatibility cannot be maintained.
- Treat API schemas, URLs, status codes, and error formats as long-term contracts.
Backward compatibility is not only a versioning strategy; it is a fundamental API design principle.