Evolving protobuf schemas safely

gRPC · Practical · 12 min

Clients and servers are deployed at different times, so every schema change must be compatible.

Old and new code will exchange messages for weeks after any change, so protobuf schemas must stay wire-compatible. The rules follow from the fact that fields are identified by number:

  • Adding a field is safe. Old readers skip unknown fields (and proto3 preserves them when re-serializing); new readers see defaults when old writers omit it.
  • Removing a field is safe only if you reserve its number and name: reserved 3; reserved "fax";. Reusing a number later would make old data decode as the wrong field.
  • Renaming a field is wire-safe (the name is not on the wire) but breaks JSON encoding and generated code.
  • Changing a type is mostly breaking. A few changes are compatible (int32 to int64, string to bytes for UTF-8) but avoid them.
  • Moving a field into or out of a oneof is risky; adding a new field to a oneof is fine.

A oneof says at most one of several fields is set (oneof payment { Card card = 4; BankTransfer bank = 5; }); setting one clears the others.

Enforce the rules mechanically: Buf (buf breaking --against '.git#branch=main') fails CI on incompatible changes, and buf lint enforces naming conventions. Newer protobuf releases also introduce editions (edition = "2023";) which replace syntax and turn features like field presence into configurable settings.

Example

syntax = "proto3";
package crm.v1;

message Contact {
  reserved 3;               // was: string fax = 3;
  reserved "fax";

  string id = 1;
  string name = 2;
  string email = 4;
  repeated string phones = 5;   // added later: old readers skip it

  oneof preferred_channel {     // at most one is set
    string sms_number = 6;
    string slack_handle = 7;
  }
}
// CI: buf breaking --against '.git#branch=main'

Remember

  • Adding fields is safe; old readers skip unknown fields
  • Reserve numbers and names of removed fields
  • Never change a field's number or meaning
  • Automate checks with buf breaking

Watch out for

  • Deleting a field and later reusing its number for a different field.

Check yourself

  1. You remove field `string fax = 3;`. What should you do?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: protobuf.dev/programming-guides/proto3/#updating