The .proto file is not just an input to code generation. It is the agreement between independently deployed services. Change it casually and generated types will compile while an old client still fails in production.
Short answer: protoc compiles message schemas into language-specific model/serialization code and service definitions into client and server stubs (with a language's gRPC plugin). Field numbers—not field names—identify data on the wire, so never renumber or reuse them.
syntax = "proto3";
service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); }
message HelloRequest { string name = 1; }
message HelloReply { string message = 1; }
| Target | Typical generated output |
|---|---|
| TypeScript | Message types/serializers plus client and server interfaces |
| Go | Structs, marshal methods, client interface, server registration |
| Java | Message classes and gRPC service base/client stubs |
Compatibility rules that are actually useful
- Add new optional fields with new numbers.
- Do not change a field's number or wire type.
- Reserve deleted field numbers and names so a future editor cannot reuse them.
- Prefer a new RPC or message field over changing the meaning of an existing one.
message User {
string display_name = 1;
reserved 2;
reserved "legacy_name";
string locale = 3;
}
Renaming a field is usually wire-compatible because the number stays the same; changing its meaning is still a contract break for humans and generated API docs.
The compiler gives you fast binary serialization and typed stubs. It cannot decide schema evolution policy for you. Put breaking-change checks in CI and treat proto review with the same seriousness as a public HTTP API review.
Cover photo by Brett Sayles on Pexels.
