proto2 vs proto3 cheat sheet
proto3 removed required, custom defaults, groups and most extensions, made enums open with a zero first value, and packs repeated numbers by default. The difference you meet most is presence: in proto3 a plain scalar set to zero is indistinguishable from one never set, unless you mark it optional.
Updated
The differences
| proto2 | proto3 | |
|---|---|---|
| Declared with | syntax = "proto2";, and the default when the line is missing | syntax = "proto3"; |
| Field labels | Every singular field needs optional or required | No label needed; optional adds presence, repeated for lists |
required | Allowed, and discouraged: removing one later breaks old readers | Removed |
| Presence of scalars | Every singular field records whether it was set | Only optional scalars and message fields do; a plain 0, "" or false is not sent |
| Default values | Custom, with [default = …] | Always the type's zero value |
| Enums | Any first value; closed, so an unknown number goes to unknown fields | First value must be 0; open, so an unknown number is kept in the field |
| Repeated numbers | Unpacked unless [packed = true] | Packed by default |
| Groups | Allowed, and deprecated | Not allowed; use a nested message |
| Extensions | extensions 100 to 199; and extend | Only to declare custom options; use Any instead |
UTF-8 in string | Not checked when parsing | Checked when parsing; invalid text is rejected |
oneof and map | Both | Both |
Worked example: one message in each
The same user record, written for each syntax.
syntax = "proto2";
message User {
required string id = 1;
optional string name = 2 [default = "anonymous"];
optional int32 age = 3;
repeated int32 scores = 4 [packed = true];
optional Role role = 5;
enum Role {
ROLE_ADMIN = 1;
ROLE_MEMBER = 2;
}
}syntax = "proto3";
message User {
string id = 1;
string name = 2;
optional int32 age = 3;
repeated int32 scores = 4;
Role role = 5;
enum Role {
ROLE_UNSPECIFIED = 0;
ROLE_ADMIN = 1;
ROLE_MEMBER = 2;
}
}idlosesrequired. In proto3 validation is the server's job; an empty string arrives as if the field were never set.nameloses its default ofanonymous. A proto3 reader sees""and must apply the fallback itself.agestaysoptionalin proto3, so the server can tell 0 from not given. Withoutoptionalit could not.scoresis packed in both, explicitly in proto2 and by default in proto3.- The proto3 enum gains
ROLE_UNSPECIFIED = 0, because the zero value is what an unset field reads as.
Paste either one into the .proto to JSON tool to see the request each accepts.
Editions
Newer files start with edition = "2023"; instead of a syntax line. Editions replace the two syntaxes with feature settings: field_presence, enum_type, repeated_field_encoding and utf8_validation each take the proto2 or proto3 behaviour, per file, message or field. Edition 2023 defaults to explicit presence like proto2, and open enums, packed fields and UTF-8 checks like proto3.
In JSON, both look the same
The ProtoJSON mapping is shared: field names in lowerCamelCase, 64-bit integers as strings, enums by name. The well-known types reference lists the special forms.
istek reads both proto2 and proto3 files, with no protoc to install, and writes the JSON request for any method in them. See istek.