Go

How do you keep protobuf messages backward and forward compatible? Why must you never reuse field numbers?

Question 569MediumGo 1.22 to 1.25

The protobuf wire format identifies each field only by its field number and wire type, never by its name. Two rules follow from this:

  • Backward compatibility (new code reads old data): a missing field decodes as its zero value.
  • Forward compatibility (old code reads new data): unknown fields are skipped, and in Go they are preserved and re-serialized.

Reusing a number breaks this. An old client, or an old message sitting in a queue or database, will decode the bytes as the old field, perhaps with a different type. You get silent data corruption or parse errors, not a compile error.

message User {
  reserved 3, 7 to 9;        // numbers of deleted fields: never reuse
  reserved "email_legacy";   // stops the old name being reused (JSON/text formats)
  string id = 1;
  string name = 2;
  optional int32 age = 4;    // explicit presence: tells "unset" apart from 0
  Status status = 5;
  repeated string tags = 6;
}
enum Status {
  STATUS_UNSPECIFIED = 0;    // the zero value must mean "unknown"
  STATUS_ACTIVE = 1;
}

Safe changes: add new fields with new numbers, delete a field and reserve its number and name, rename a field (binary-safe but breaks JSON), and add enum values when readers handle unknown values.

Unsafe changes:

  • Changing a field's number or type. There are a few wire-compatible exceptions, such as int32/int64/uint32/uint64/bool, but values can be truncated.
  • Moving existing fields into or out of a oneof.
  • Changing a singular field to repeated for message types.
  • Making a field required (proto2).

Gotchas:

  • With proto3 scalars, 0 and "not set" look the same unless you declare the field optional.
  • Compare messages with proto.Equal, not == or reflect.DeepEqual.
  • Never copy generated structs by value. They embed a no-copy guard that go vet flags.
  • Enforce these rules in CI with buf breaking.

More on Observability, Debugging & Production Operations

All 14 Observability, Debugging & Production Operations questions