Handling Backward Compatibility

1. Adding New Fields Safely

RuleDetail
Pick new tagUnused number — never reuse
Default behaviorOld clients ignore unknown fields
Don't change semanticsOf existing fields

2. Deprecating Fields

Example: Deprecated option

message User {
  string name = 1 [deprecated = true];
  string display_name = 4;
}
StepDetail
Mark deprecatedCodegen warns on use
Document replacementIn proto comment
Remove laterOnly after all clients migrated

3. Reserving Field Numbers

Example: Reserve after delete

message User {
  reserved 2, 3, 7 to 10;
  reserved "email", "phone";
}

4. Versioning Services

StrategyDetail
Package versionuser.v1, user.v2
Run side-by-sideUntil v1 deprecated
Avoid in-place breaking changeAlways cut new version

5. Handling Schema Evolution

ChangeSafe?
Add optional fieldYes
Remove fieldReserve number — yes
Change typeNo (mostly)
Rename fieldYes (wire uses number) — bad for JSON consumers
Change cardinalityRepeated ↔ singular: NO

6. Using Buf Breaking Detection

Example: buf breaking

buf breaking --against '.git#branch=main'
CategoryDetail
FILEStrict — any file change risky
PACKAGE (default)Same package compat
WIREOnly true wire-breaking
WIRE_JSONWire + JSON name compat

7. Maintaining API Compatibility

PracticeDetail
Contract testsOld client × new server in CI
Schema registryCentral .proto with PR review
Long deprecationMin one release cycle

8. Migrating Clients

StepDetail
Dual-publishv1 and v2 simultaneously
Adapter layerServer proxies v1 → v2 internally
Feature flagToggle client version

9. Supporting Multiple Versions

PatternDetail
Side-by-side servicesRegister both on same server
Shared business coreBoth wrap same domain layer
Sunset headerCommunicate end-of-life date

10. Sunsetting Old Versions

PhaseDetail
AnnounceSunset header + docs
WarnLog per-call deprecation
Block 0%/10%/100%Gradual rollout of unavailability
RemoveDelete package; cannot be undone