← Back to the library
BackendApplied · 8 min

After a proto change, old clients read the wrong values

“After the service shipped a new .proto, older clients still parse responses without errors, but prices show up as zero, a status reads as the first enum value, or a field holds another field's data. Some clients in other languages fail to parse the response at all.”

THE PRINCIPLE

A field number belongs to one meaning forever. To change the meaning, add a new field.

FIRST MOVES

  1. Diff the .proto against the version old clients were built from. Look for reused or renumbered field numbers, changed types and enum values that moved.
  2. Restore the old field number and meaning. Put the new meaning on a new number and keep `reserved` entries for deleted numbers and names.
  3. Check type changes against the compatibility table. int32/int64/uint32/uint64/bool interconvert but truncate; string vs int64 does not. Renames are wire-safe but break JSON and generated code.
  4. Make every enum start with an `_UNSPECIFIED = 0` value. Unset fields and old data decode to zero, so zero must never be a real state like ACTIVE.
  5. Run `buf breaking --against '.git#branch=main'` in CI (FILE is the default rule category, WIRE_JSON or WIRE if only the encoding matters) so these diffs fail the PR.

TOOLS: THEN → NOW

Code review catches field reusebuf breaking in CI (bufbuild/buf-action) against main or the BSR
Delete unused fields outrightDelete them and add `reserved` numbers and names in the same change

PATTERN SNAPSHOT

message Order {
  reserved 3, 7;                    // deleted: never reuse these numbers
  reserved "coupon_code", "total";  // keeps JSON and TextFormat safe too

  string id = 1;
  OrderStatus status = 2;
  int64 total_minor_units = 8;      // new meaning, new number (was float total = 7)
}

enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0;     // what unset and unknown-to-old-code decode to
  ORDER_STATUS_PENDING = 1;
  ORDER_STATUS_PAID = 2;
}

CLOSE THE AI. EXPLAIN THIS.

Why can deleting field 7 and later adding an unrelated field 7 corrupt data without any parse error, and which keyword stops it?

HOW IT WORKS UNDERNEATH

SOURCES

Guide reviewed