Contract Evolution and Backward Compatibility
Version proto schemas and manage field changes without breaking existing consumers.
Why Contract Evolution Matters
In a microservice mesh, a gRPC service contract is a shared API used by many independent consumers that deploy on their own schedule. You can almost never coordinate a simultaneous upgrade of every client and server.
The goal of contract evolution is to change a .proto schema so that:
- Old clients keep working against a new server (backward compatibility).
- New clients keep working against an old server (forward compatibility).
Protobuf was designed precisely for this. The wire format encodes fields by tag number, not by name, and unknown fields are tolerated rather than rejected. Master a few rules and you can evolve contracts for years without a breaking flag day.
The Golden Rule: Never Reuse Field Numbers
On the wire, every field is identified by its tag number plus a wire type. The field name is irrelevant once compiled. This single fact drives almost every evolution rule.
- You may freely rename a field — the number is what travels.
- You may add a new field with a fresh, never-before-used number.
- You must never reuse a number that was previously assigned to a different field.
Reusing a number means an old client that still sends data under that tag will be silently misinterpreted by the new schema as a completely different field — corrupting data with no error.
// user.proto (v1)
syntax = "proto3";
package user.v1;
message User {
string id = 1;
string email = 2;
string display_name = 3;
}
// Tag numbers 1,2,3 are now a permanent part of the contract.
// They may never be re-assigned to a different field.All lessons in this course
- Defining Services and Messages in Protobuf
- Implementing and Consuming gRPC Methods
- Streaming RPCs and Backpressure
- Contract Evolution and Backward Compatibility