Editions replace syntax = "proto2" and "proto3" with named
features. A file says which edition it is in (edition = "2023";), and each
edition sets a default for every feature. A file, message, field or enum can override
a feature. The wire format does not change; features decide which of the existing
encodings a field uses and how a reader treats what it finds.
Features and their defaults
| Feature | 2026 | 2024 | 2023 | proto3 | proto2 |
|---|---|---|---|---|---|
field_presence | EXPLICIT | EXPLICIT | EXPLICIT | IMPLICIT* | EXPLICIT |
enum_type | OPEN | OPEN | OPEN | OPEN | CLOSED |
repeated_field_encoding | PACKED | PACKED | PACKED | PACKED | EXPANDED |
utf8_validation | VERIFY | VERIFY | VERIFY | VERIFY | NONE |
message_encoding | LENGTH_PREFIXED | LENGTH_PREFIXED | LENGTH_PREFIXED | LENGTH_PREFIXED | LENGTH_PREFIXED |
json_format | ALLOW | ALLOW | ALLOW | ALLOW | LEGACY_BEST_EFFORT |
enforce_naming_style | STYLE2026 | STYLE2024 | STYLE_LEGACY | STYLE_LEGACY | STYLE_LEGACY |
default_symbol_visibility | STRICT | EXPORT_TOP_LEVEL | EXPORT_ALL | EXPORT_ALL | EXPORT_ALL |
enforce_proto_limits | PROTO_LIMITS2026 | LEGACY_NO_EXPLICIT_LIMITS | LEGACY_NO_EXPLICIT_LIMITS | LEGACY_NO_EXPLICIT_LIMITS | LEGACY_NO_EXPLICIT_LIMITS |
* A proto3 field with the optional label has explicit presence.
Field presence: field_presence
Whether a field that holds its default value is written, and whether a reader can tell it was set.
| Value | Meaning | Syntax equivalent |
|---|---|---|
EXPLICIT | Tracks presence: a set field is always written, and the generated has_ function tells whether it was set. | proto2 optional, proto3 optional |
IMPLICIT | No presence: a field holding its default is not written, and a reader cannot tell it from an unset one. | proto3 singular fields |
LEGACY_REQUIRED | The field must be set for parsing and serialising to succeed. | proto2 required |
Explicit [default = …] values exist only for proto2 and Editions fields
with explicit presence: a reader gives the declared default for a missing field, and
the default itself is never in the file. See also
missing fields.
Open and closed enums: enum_type
| Value | Meaning |
|---|---|
OPEN | A number the enum does not define is kept in the field. proto3 and the Editions default. |
CLOSED | A number the enum does not define goes to the message’s unknown field set; the field stays unset. proto2. |
The closed behaviour differs by field shape:
- Singular: the value goes to the unknown fields and the field is unset.
- Repeated: unknown values go to the unknown fields and are written back after the
known ones, so
[0, 2, 1, 2]with only 0 and 1 known parses as[0, 1]and comes out as[0, 1, 2, 2]. - Map with an enum value: the whole entry, key and value, goes to the unknown fields.
The difference is in what the reader does with the number; the bytes on the wire
are the same. The feature legacy_closed_enum in C++ and Java forces
closed behaviour for one field, mirroring proto2 enums imported into proto3 files.
Encodings: repeated_field_encoding and message_encoding
| Feature and value | Encoding on the wire |
|---|---|
repeated_field_encoding = PACKED | Repeated numbers share one LEN field. |
repeated_field_encoding = EXPANDED | Each repeated number has its own tag. |
message_encoding = LENGTH_PREFIXED | A nested message is a LEN field. |
message_encoding = DELIMITED | A nested message is a group, with a start and end tag and no length. |
UTF-8: utf8_validation
VERIFY makes the parser check that string fields are UTF-8
and fail if not. NONE treats them like unverified bytes;
this is what proto2 strings do. The (pb.java).utf8_validation feature can
force verification in Java.
Language and tool features
| Feature | Values |
|---|---|
json_format | ALLOW: a well-defined JSON mapping is required. LEGACY_BEST_EFFORT: mappings that can be many-to-one are tolerated. |
enforce_naming_style | STYLE2026, STYLE2024, STYLE_LEGACY: how strictly the style guide’s naming rules are checked. |
default_symbol_visibility | EXPORT_ALL, EXPORT_TOP_LEVEL, LOCAL_ALL, STRICT: which messages and enums are visible to other files. |
enforce_proto_limits | PROTO_LIMITS2026: at most 1,500 fields a message, 1,000 oneofs, 1,200 fields a oneof and 1,700 values an enum. LEGACY_NO_EXPLICIT_LIMITS: none of these. |
These affect the compiler and generated code only. None of them changes the bytes.
Reading a file without the schema
Features are in the schema, not the message, so a file shows only which encoding a
field used. A packed field and a repeated field with one value differ on the wire;
a group and a message_encoding = DELIMITED field look the same. A
descriptor set records each file’s edition and features, and the explorer shows them
in its FeatureSet
fields.