Your gRPC backend serves an iOS app. Apple takes 2-7 days to review app updates. Meanwhile, you need to ship a backend change that adds new fields to a response and removes an old one. But 40% of your users are still on the old app version. If you break the wire format, those users see crashes. Protobuf schema evolution rules tell you exactly which changes are safe and which are not.
How Protobuf Wire Format Works
Protobuf serializes by FIELD NUMBER, not field name:
message User {
string name = 1; // field number 1
string email = 2; // field number 2
int32 age = 3; // field number 3
}
Wire format (binary):
[field_num=1][type=string][value='Alice']
[field_num=2][type=string][value='alice@x.com']
[field_num=3][type=int32][value=30]
Field NAMES are only for human readability.
The wire only sees NUMBERS and TYPES.
This is why renaming a field is safe but
changing its number is catastrophic.
Safe Changes (Backwards Compatible)
SAFE - Adding a new field:
// v1 (old client knows this)
message User {
string name = 1;
string email = 2;
}
// v2 (new server adds field 3)
message User {
string name = 1;
string email = 2;
string avatar_url = 3; // NEW - old clients ignore it
}
// Old client sees unknown field 3 -> skips it. No crash.
// New client sees field 3 -> uses it. Everyone happy.
SAFE - Renaming a field:
// Wire format uses numbers, not names
string user_name = 1; -> string full_name = 1; // SAFE
SAFE - Adding a new RPC method:
// Old clients never call it, so they are unaffected
SAFE - Adding a new enum value:
// Old clients treat unknown enum values as default (0)
Breaking Changes (NEVER Do These)
BREAKING - Changing a field number:
string name = 1; -> string name = 5; // DISASTER
// Old clients send field 1, new server expects field 5
// Data silently disappears or gets misinterpreted
BREAKING - Changing a field type:
int32 age = 3; -> string age = 3; // DISASTER
// Binary encoding for int32 and string are incompatible
// Deserialization fails or produces garbage
BREAKING - Removing a field without reserving:
// v1: string email = 2;
// v2: (removed email)
// v3: int32 login_count = 2; // REUSED field 2!
// Old clients sending email string into int32 field = crash
CORRECT way to remove:
message User {
string name = 1;
reserved 2; // lock field number forever
reserved 'email'; // lock field name forever
string avatar_url = 3;
}
Schema Evolution Cheat Sheet
Change Safe? Why
---------------------- ------- ---------------------------
Add new field YES Old clients skip unknown
Rename field YES Wire uses numbers, not names
Add new RPC YES Old clients do not call it
Add enum value YES Unknown -> default (0)
Change field number NO Breaks all existing clients
Change field type NO Wire encoding incompatible
Remove field (no rsrv) NO Number might be reused
Remove field (reserved) YES Number locked, safe
Change optional->req NO Deserialization failures
Interview Tip
When discussing gRPC API evolution, say: 'Protobuf uses field numbers for wire serialization, so renaming fields is always safe. Adding new fields is the primary evolution mechanism - old clients simply ignore unknown fields. I would never reuse a deleted field number - instead I mark it reserved forever. For major breaking changes, I would create a new service version (v2.UserService) rather than modifying the existing contract. This lets me roll out changes gradually while old clients continue working.'
<Architecture overview
/blockquote>
Key Takeaway
Protobuf uses field numbers (not names) for serialization. Adding fields is safe. Changing types or numbers is breaking. Never reuse deleted field numbers - reserve them. These rules let you evolve gRPC services without coordinated client-server deployments.
No comments yet. Be the first to start the thread!
Related Articles
Enjoyed this content?
Your support keeps us creating free resources
We put a lot of hours into researching and writing these guides. If it helped you, consider buying us a coffee. Every bit goes toward keeping ScaleDojo's content free and growing.
$
One-time payment via Stripe. ScaleDojo account required.