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

proto2proto3
Declared withsyntax = "proto2";, and the default when the line is missingsyntax = "proto3";
Field labelsEvery singular field needs optional or requiredNo label needed; optional adds presence, repeated for lists
requiredAllowed, and discouraged: removing one later breaks old readersRemoved
Presence of scalarsEvery singular field records whether it was setOnly optional scalars and message fields do; a plain 0, "" or false is not sent
Default valuesCustom, with [default = …]Always the type's zero value
EnumsAny first value; closed, so an unknown number goes to unknown fieldsFirst value must be 0; open, so an unknown number is kept in the field
Repeated numbersUnpacked unless [packed = true]Packed by default
GroupsAllowed, and deprecatedNot allowed; use a nested message
Extensionsextensions 100 to 199; and extendOnly to declare custom options; use Any instead
UTF-8 in stringNot checked when parsingChecked when parsing; invalid text is rejected
oneof and mapBothBoth

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;
  }
}
  • id loses required. In proto3 validation is the server's job; an empty string arrives as if the field were never set.
  • name loses its default of anonymous. A proto3 reader sees "" and must apply the fallback itself.
  • age stays optional in proto3, so the server can tell 0 from not given. Without optional it could not.
  • scores is 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.

istek, $29 once

macOS 14.0+ · Apple Silicon · no account, no telemetry

Buy istek · $29